Sunteți pe pagina 1din 36

THE LEADING DEVELOPERS JOURNAL DEDICATED TO .

NET TECHNOLOGIES
Presented by

THE WORLDS LEADING .NET RESOURCE

2007 Volume 5 Issue6

Dealing with Legacy Projects


10

Using XML with Stored Procedures Effectively in SQL Server 2005 Indexed LINQ
Presorted Standard US Postage PAID St. Croix Press

A Roadmap
Register Today!

for Java Professionals Early Bird: SAVE $100

W W W . D O T N E T D E V E L O P E R S J O U R N A L . C O M

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Editorial Editorial Editorial Editorial

dotnetboard@sys-con.com Editor-in-Chief Patrick Hynds phynds@criticalsites.com Group Publisher Roger Strukhoff roger@sys-con.com Mobility Editor Jon Box jon.box@microsoft.com Contributing Editor Derek Ferguson derekf@speakeasy.net Open Source Editor Dennis Hayes dennisdotnet@yahoo.com Product Review Editor Doug Holland doug.holland@precisionobjects.com VB Editor Keith Franklin keithf@magenic.com Smart Client Editor Tim Huckaby timh@interknowlogy.com BizTalk Editor Brian Loesgen brian.loesgen@neudesic.com

EDITORIAL BOARD

Security, Vista and the Developer


By Patrick Hynds

Security Editor Duane Laflotte dlaflotte@criticalsites.com


dotnetadvisors@sys-con.com Derek Ferguson derekf@magenic.com Jeremy Geelan jeremy@sys-con.com Thom Robbins trobbins@microsoft.com John Gomez John.Gomez@eclipsys.com Scott Hanselman scott@hanselman.com Dean Guida deang@infragistics.com John Sharp johns@contentmaster.com Jacob Cynamon jacobcy@microsoft.com Chris Mayo cmayo@microsoft.com Gary Cornell gary@thecornells.com Joe Stagner joestag@microsoft.com Peter DeBetta peter@debetta.com

ADVISORY BOARD

Executive Editor Nancy Valentine nancy@sys-con.com

For subscriptions and requests for bulk orders, please send your letters to Subscription Department Subscription Hotline: subscribe@sys-con.com Cover Price: $6.99/issue Domestic: $69.99/yr. (12 issues) Canada/Mexico: $99.99/yr. Overseas: $129.99/yr. (u.s. banks or money orders). Back issues: $12/ea., plus shipping and handling. SYS-CON Media 577 Chestnut Ridge Rd., Woodcliff Lake, NJ 07677 Telephone: 201 802-3000 Fax: 201 782-9601 .NET Developers Journal (ISSN#1541-2849) is published monthly (12 times a year) for $69.99 by SYS-CON Publications, Inc., 577 Chestnut Ridge Road, Woodcliff Lake, NJ 07677. Postmaster: Send address changes to: .NET Developers Journal, SYS-CON Publications, Inc., 577 Chestnut Ridge Road Woodcliff Lake, NJ 07677.
Copyright 2007 by SYS-CON Publications, Inc. All rights reserved. No part of this publication may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopy or any information storage and retrieval system, without written permission. For promotional reprints, contact Reprint Coordinator Megan Mussa, megan@sys-con.com. Worldwide Newsstand Distribution Curtis Circulation Company, New Milford, NJ Newsstand Distribution Consultant: Gregory Associates / W.R.D.S. 732 607-9941 - BJGAssociates@cs.com For list rental information: Kevin Collopy: 845 731-2684, kevin.collopy@edithroman.com; Frank Cipolla: 845 731-3832, frank.cipolla@epostdirect.com All brand and product names used on these pages are trade names, service marks, or trademarks of their respective companies. SYS-CON Publications, Inc., is not affiliated with the companies or products covered in .NET Developers Journal. .NET and .NET-based marks are trademarks or registered trademarks of Microsoft Corporation in the United States and other countries. SYS-CON Publications, Inc., reserves the right to revise, republish and authorize its readers to use the articles submitted for publication.

SUBSCRIPTIONS

EDITORIAL OFFICES

ista is getting some traction as a client OS now and that means developers are starting to see on the horizon that they should begin to support it. This is good and bad. Good because there are lots of cool things for developers in Vista, but bad because Vista changes the game quite a bit on developers relative to security. On the good side Vista has lots more features and functionality, such as the new rewall API and the introduction of gadgets; the bad focuses mostly on trying to do things as a developer on Vista. Let me back up a bit. Security is good, of course, but it is often the case that usability and security are in an inverse relationship. The more secure I make my house, the more likely I will end up calling a locksmith from my porch. Vista has User Access Control (also known as UAC), which prompts the user for permission to do anything that could be construed as above the norm security-wise. It turns out that pretty much everything a developer does is above the norm security-wise, and that is a problem in a world where developers who dont code on the target platform often get killed for writing incompatible apps. This has resulted in a legion of developers that I know who have turned back or stopped at the brink of using Vista for their development. You can turn off UAC and other features, but there is also the fact that older versions of Visual Studio (anything older than Visual Studio 2005, in fact) are not compatible or supported on Vista. Even Visual Studio 2005 has issues as of this writing that some argue make it effectively unsupported. While the latter will certainly resolve itself soon, the fact remains that if you target Visual Studio 2003 or earlier, then life is hard on Vista. Alternatives such as using virtual machines abound, but they all cost something in terms of productivity and sometimes sanity for the developer. Maybe this is just the price that we pay for notching up the security of an operating system. Still it seems the developer bears much more of the brunt than others...

About the Author...


Patrick Hynds is the Microsoft Regional Director for Boston, the president of CriticalSites, and has been recognized as a leader in the technology eld. An expert on Microsoft technology (with, at last count, 55 Microsoft certications) and experienced with other technologies as well, he previously taught freelance software development and network architecture. Prior to joining CriticalSites, Patrick was a successful contractor who enjoyed mastering difcult troubleshooting assignments. A graduate of West Point and a Gulf War veteran, he brings an uncommon level of dedication to his leadership role at CriticalSites. phynds@criticalsites.com

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

Inside DNDJ
EDITORIAL

Security, Vista and the Developer


By Patrick Hynds ..........................................................................................................

MONKEY BUSINESS

Silverlight and Moonlight


By Dennis Hayes ..........................................................................................................

Dealing with Legacy Projects


Do you COM?
By Catalin Sandu

10
INDEXES

Indexed LINQ

Optimizing the performance of LINQ queries using in-memory indexes


By Aaron Erickson.......................................................................................................

28

Using XML with Stored Procedures Effectively in SQL Server 2005


How things changed
By Srinivas K. Surampalli

PRODUCT REVIEW

Active Endpoints ActiveBPEL


By Dennis Hayes ..........................................................................................................

TM

34

20
Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

BPEL is the is the BPEL is the BPEL SOA SQL of SOA of SQL of SOA the SQL SOA
Get started building next-generation Get started building next-generation SOA applications with the leading vendor of SOA applications with the leading vendor of BPEL technologies Getnext-generation SOA applications started building next-generation Build BPEL technologies SOA applications with the leading vendor of

BPEL active-endpoints.com/soa active BPEL active er software today activeBPEL


activeBPEL
BPEL consulting and training. BPEL design tools, servers and source code for Eclipse, Apache Tomcat, JBoss, BPEL consulting and training. WebSphere, WebLogic, BizTalk and Microsoft .NET. BPEL design tools, servers and source code for Eclipse, Apache Tomcat, JBoss, WebSphere, WebLogic, BizTalk and Microsoft .NET. Copyright 2006 Active Endpoints, Inc. training. BPEL consulting and All Rights Reserved. All product names are trademarks or service marks of their respective companies. BPEL design tools, servers and source code for Eclipse, Apache Tomcat, JBoss, Copyright 2006 Active Endpoints, Inc. All Rights Reserved. WebSphere, WebLogic, BizTalk and Microsoft companies. All product names are trademarks or service marks of their respective .NET.
5

with the leader in BPEL technologies BPEL technologies Download BPEL tooling & server software today generation Download BPEL tooling & server software today ding vendor of es Download BPEL tooling & server software today

Copyright 2006 Active Endpoints, Inc. All Rights Reserved. g. All product names are trademarks or service marks of their respective companies. se, Apache Tomcat, JBoss, Visit us at www.dotnetdevelopersjournal.com June 2007 Volume: 5 Issue: 6 rosoft .NET.


4 4 4 4

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business Monkey Business

Silverlight and Moonlight


By Dennis Hayes

icrosoft has a new set of technologies called Silverlight that are meant to bring rich multimedia to browsers and portable devices. They have released two versions: a full release of version 1.0 and a beta version of 1.1. Version 1.0 is not very interesting, but the 1.1 beta is totally different and is making a big splash. It is .NET based, and of course Mono is working furiously on it. In fact, after Miguel saw Silverlight for the rst time at the Microsoft MIX 07 conference in Las Vegas and was offered the chance to demo Moonlight (the Mono version of Silverlight) at Microsoft Re-MIX 07 in Paris in 21 days, the whole Mono team started on a 21-day death march to implement Moonlight in time to demo it in Paris. After which, Miguel said The past 21 days have been some of the most intense hacking days that I have ever had and the same goes for my team that worked 12 to 16 hours per day every single day including weekends to implement Silverlight for Linux in record time. I know the long hard days Miguel and the Mono team typically work, and when he starts talking about the most intense hacking days he has ever had, part of me wishes I had been part of it, and part of me is glad I wasnt. What can a bunch of monkeys banging on a bunch of typewriters for 21 days create? The complete works of Shakespeare? Nope. Check out Figure 1. Joshua Allen from Microsoft was impressed with the Mono hack-a-thon and blogged about it a bit at http://visitmix.com/ (see the June 21 entry). Note that Mono has not completed implementing the entire Silverlight suit, but the screenshots show how much progress was made in just 21 days. For more screenshots, see http://www. mono-project.com/MoonlightShots. If you go to the bottom of the page and scroll up, you can see how Moonlight progressed. The Moonlight homepage is at http://www.mono-project.com/

Moonlight, and the Silverlight homepages are at http://silverlight.net/ and http://www.microsoft. com/silverlight/. You can also see video of Moonlight animation and video capture on Miguels blog at http://tirania.org/blog/index.html (see the June 24 entries). Moonlight is being developed as part of the Mono Olive Project (the Mono implementation of .NET 3.0). The implementation is now being discussed at http://groups. google.com/group/mono-olive. There are a number of parts that combine to form Silverlight, including a new security model (see a good whitepaper at http://blogs.msdn.com/ shawnfa/archive/2007/05/09/the-silverlight-security-model.aspx), video codecs, and a new version of the .NET libraries labeled .NET 2.1 (aka WPF/E). .NET 2.1 is both a subset (similar to, but different from, the compact framework), and a super set (adds libraries to support the new functionality); dont even get me started on how it relates to .NET 3.0 and 3.5.

C#3.0
Mono continues to work on C#3.0 and this month brings full support for the specication for implicitly typed local variables and implicitly typed arrays. I added for the specication because the specication does not support implicitly typed multidimensional arrays, but the Microsoft C# 3.0 compiler does support them; it is not clear if this is an enhancement or a bug; once this is decided, the Mono team will code accordingly.

Google Summer of Code


Students involved with the Google Summer of Code, including those working on Mono, are making lots of progress. Google is having a major impact not only on these students lives, but also on the many projects they are working on. I would like to tell you all the cool things the students are doing just in the Mono project, but I dont even have time for the highlights this month. Go check them out at http://groups.google.com/group/mono-soc-2007 and http://planet-soc.com/.
About the Author
Dennis Hayes is a programmer at Georgia Tech in Atlanta, Georgia, where he writes software for the Adult Cognition Lab in the Psychology Department. He has been involved with the Mono project for over ve years. dennisdotnet@yahoo.com

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

Book Review

Amazon.com Top 10 .NET Books


10
Sams Teach Yourself Microsoft Visual Basic .NET 2003 in 21 Days, Second Edition
Holzner, Steve

Microsoft .NET XML Web Services Step by Step


Freeman, Adam

President and CEO Fuat Kircaali fuat@sys-con.com Group Publisher Roger Strukhoff roger@sys-con.com

ADVERTISING
Senior Vice President, Sales and Marketing Carmen Gonzalez carmen@sys-con.com

MCAD/MCSD Training Guide (70-315): Developing and Implementing Web Applications with Visual C# and Visual Studio.NET
Kalani, Amit

MCAD/MCSD Self-Paced Training Kit: Developing Web Applications with Microsoft Visual Basic .NET and Microsoft Visual C# .NET, Second Edition
Webb, Jeff

Advertising Sales Director Megan Mussa megan@sys-con.com Associate Sales Manager Corinna Melcon corinna@sys-con.com Events Manager Lauren Orsi lauren@sys-con.com Events Associate Sharmonique Shade sharmonique@sys-con.com

PRODUCTION

MCAD/MCSD Self-Paced Training Kit: Developing Windows-Based Applications with Microsoft Visual Basic.NET and Microsoft Visual C#.NET, Second Edition
Stoecker, Matthew A.

Lead Designer Abraham Addo abraham@sys-con.com

Programming Microsoft Windows CE .NET, Third Edition


Boling, Douglas

Art Director Alex Botero alex@sys-con.com Associate Art Director Louis F. Cuffari louis@sys-con.com Assistant Art Director Tami Lima Tami@sys-con.com

WEB SERVICES
VP, Information Systems Bruno Decaudin bruno@sys-con.com Information Systems Consultant Robert Diamond robert@sys-con.com

.NET and COM: The Complete Interoperability Guide (2 Volume Set)


Nathan, Adam

MCAD/MCSD Self-Paced Training Kit: Microsoft .NET Core Requirements, Exams 70-305, 70-315, 70-306, 70-316, 70-310, 70-320, and 70-300 box vol. set
Microsoft Corporation

Web Designers Stephen Kilmurray stephen@sys-con.com Richard Walter richard@sys-con.com

ACCOUNTING
Financial Analyst Joan LaRose joan@sys-con.com Accounts Payable Betty White betty@sys-con.com

Test-Driven Development in Microsoft .NET (Microsoft Professional)


Newkirk, James W.

SUBSCRIPTIONS
201 802-3012 888 303-5282 subscribe@sys-con.com

Programming Microsoft Visual Basic .NET Version 2003 (Book & CDROM)
Balena, Francesco

CUSTOMER RELATIONS
Circulation Service Coordinators Edna Earle Russell edna@sys-con.com Alicia Nolan alicia@sys-con.com

PROVIDED BY

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Figure 1: Silverlight-airlines-demo.png

MonoDevelop
MonoDevelop, the SharpDevelop fork for Mono, has released version 0.14. The MonoDevelop team has made a lot of progress on this release, and they now plan to make two more big releases in the next three months, and then do a feature freeze to prepare for the big version 1.0 release. This release includes a subversion add-in, refactoring, smart C# indenting, and importing/exporting Visual Studio 2005. The next releases are planned to include key binding, improved ASP .NET support, and, from the Summer of Code students, C/C++ support and improved make le integration. Full release notes are at http://www. monodevelop.com/Release_ notes_for_MonoDevelop_0.14.

Facebook
Tyler has posted information on how to use Mono to create Facebook applications; see his Weather# application example at http://www.unethicalblogger.com/ posts/tyler/mono_meet_facebook.

Odds and Ends


Also Marcos Cobena (a summer of code student) has described how to compile Olive (.NET 3.0) on Mono at http://www.youcannoteatbits.org/Blog/ Archives/2007-April.html#Saturday%2c+April+21% 2c+2007.

Banshee, the music player, now runs on Windows (screenshot at http://bp3.blogger.com/_ vUUhoww_aGI/RnnZTn6WDSI/AAAAAAAAAOc/ G6lyyAmfS5Q/s1600-h/itsalive.jpg). Mono has a list of what is needed to have complete .NET 2.0 compatibility at http://www.monoproject.com/Completing2.0Prole; currently the list is about 214 items long. Autosize is coming to System.Windows.Forms. This is one of the biggest missing pieces left in Winforms; if you look through the Moma reports, it appears very often, because VisualStudio sets it for most controls (to true or false depending on the control). Also mojoPortal 2.2.2.8 has been released, and Mono is again being built from the same source code, but with a switch that turns off WebParts; see more at http://www.mojoportal.com/ download.aspx. Brian Nickels shows how to make any application a Web server at http://kerrick.wordpress.com/2007/06/12/make-any-net-app-a-webserver/. The last few columns have been so packed that I have neglected to mention that the March issue began my fth year of writing Monkey Business for .NET Developers Journal .

Microsoft has a new set of technologies called Silverlight that are meant to bring rich multimedia to browsers and portable devices
Visit us at www.dotnetdevelopersjournal.com June 2007 Volume: 5 Issue: 6

Feature

Dealing with Legacy Projects


Do you COM?

By Catalin Sandu

About the Author


Catalin Sandu is a software developer at RomSoft (www.rms.ro) and has 10 years of experience. He is both a Microsoft Certied Professional (on C++ and .NET) and an Advanced ColdFusion MX 7 Developer. Catalin has also been a member of the British Computer Society since 2005. cat@rms.ro

ou might be tempted to say that once you enter the .NET world, youll never look back. Nothing seems too easy for you at this moment, what with the brand-new .NET 3.0 thats just out, high tech and still unexplored in its entirety. However, there are situations in which the past catches up, raising the legitimate question of how you can modify your existing .NET projects to allow for interaction with the old Win32 programming model.

Why would a programmer do that? For one, there are those old legacy projects your customers like to use, and nothing can convince them (yet, you hope) to upgrade to the latest and fastest platform out there, thank you very much. Or if youre among those lucky few whose clients are always eager to take the extra step and keep up with the latest technologies, maybe you still have some in-house projects (Visual Basic 6, anyone?) that are too complicated and would take a lot of time and money to convert to your platform of choice.

10

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Feature

An elegant solution to these problems is to expose your constructs as Component Object Model (COM) objects that can be used without any problem from any Win32 program that can play with COM. The .NET platform allows you to do just that via a fancy intermediate object called COM Callable Wrapper, or CCW, that sits between the COM client and your managed code. More on this later. This article will describe what it takes to expose .NET components as COM objects. Youll nd that the job is not so difcult; in fact, most of the time what youll do is play with some new attributes that would decorate your classes, class members, and methods. Youll even learn how to create an ActiveX control in .NET, something thats still hot off Microsofts hands. But until then, lets start with...

[assembly: ComVisible(true)]

The Basics
The rst thing we need for our purposes is, obviously, a normal, not COM-exposed .NET class, which Ill change into a COM-visible component. Whats more convenient than a hello-there-like component, like the one shown below:
public class HelloClass { public string SayHelloTo(string person) { _name = person; return Hello there, + _name; } private string _name; }

Before modifying this class for COM, I need to do a quick detour and explain some of the theories involved in exposing a .NET class to a COM client. The framework component that is of help here is called COM Callable Wrapper (CCW). This is the middleman that stays between your .NET component and the actual consumer. Every call to your class will pass through this proxy, which will take care of the usual COM behaviors for you, like reference counting. When the reference count reaches zero, the .NET object becomes a good candidate for the next round of the framework garbage-collection process. One more thing about CCW: unlike normal .NET objects, the CCW is allocated from an uncollected heap. This is, in fact, the reason why the wrapper can be used from COM clients in the rst place. The CCW object is created on the y, making everything transparent for you, the .NET class designer. Coming back to our hello-there class, the rst step in the COM-.NET dance is to mark your assembly as being COM visible. For this, you need to go to the AssemblyInfo.cs le for your project (assuming youre writing a C# solution) and change the ComVisible attribute from false (the default) to true, like this:

The effect is that everything thats written in the .NET project will be exposed to the COM world. Well, at least this is the short answer; the real story will get revealed in a moment. The same thing can be accomplished if you go to your projects properties, select the Application section, and then click on the Assembly Information button. The last option in the dialog box that appears is Make assembly COM-visible, and checking it will take care of changing the ComVisible attribute to true. Still, ComVisible has other interesting uses as well, which you need to understand if you have to master the COM game as seen from a .NET vantage point. Whats the point of getting everything for free if you dont get your feet wet, right? Once you mark an assembly as being COM-visible, everything inside can be accessed by COM clients for free, but this is only partly true. What will actually get exposed is the stuff in the assembly thats public. Thats right; all thats private, protected, or internal wont see the COM light, ever. In our hello-there sample, the private data member _name is not exposed at all. The same is true about all static methods and elds from your constructs. The list of things that wont be accessible includes abstract classes, too. Whats more, your public data members will become properties for the new COM object. Did I mention that all types that really need to be exposed should have a default constructor? The ComVisible attribute, which can be used on every possible level (assembly, class, structure, elds, methods, so on), comes in handy when deciding which public parts of your library will get exposed. When using ComVisible(false) on a public method, for example, it wont be visible from COM clients. However, you can still use it from other .NET applications. Now that you know how to play nice with COM, all that remains to be done is to... yes, you guessed it register the resulting assembly in the Windows Registry, so that other applications know about it and use it. The next stop will be...

The Registration Story: The .NET Way


Normally, a COM library must be added to the Windows Registry using the RegSvr32.exe utility. Not so for .NET components. Remember, RegSvr32. exe is used to register/unregister native Win32 DLLs and ActiveX controls, and wont work on the resulting .NET library. Enter the RegAsm.exe command line tool or the Assembly Registration Tool. This small application will take a .NET component, read its metadata, determine what constructs (classes, structures, so on) are marked as COM-visible, and write the

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

11

Feature

necessary entries in the registry. This friendly tool is smart enough to do a little bit more than that, such as generating a type library for the exposed component, or creating a registry script containing the registration details. Neat, isnt it? You might want to play with the tool to see what other options are available. Assuming that our hello component compiles to a Hello.dll library, heres how you can register it:
RegAsm.exe Hello.dll

namespace This.Is.A.Very.Looong.Loooong.Namespace { [ProgId(My.Shorter.ProgID)] public class MyClass { } }

To verify that the component has been correctly added to the Windows Registry, open the OleView tool. Expand the Grouped by Component Category node, then go to the .NET Category node. Now, assuming that the main namespace where the HelloClass type has been dened is called Hello, you will nd your COM object here (search for Hello. HelloClass). To perform the reverse operation, all thats needed is to add the /unregister (or /u) switch to the above command line. If youre not happy with the default programmatic identier generated for this class, there is an alternative: just use ProgIdAttribute on your class. Normally, the programmatic identier that gets written in the registry is composed of the namespace where the class is dened and the name of the class. This might result in a not-tooeasy to remember ProgID if the namespace chain is too long. ProgIdAttibute takes care of this, giving you control over this aspect too. Note that ProgIDs are limited to 39 characters, including punctuation (only dots are accepted.) To use this attribute, refer to the following snippet:

Here, the default ProgID would have been This. Is.A.Very.Looong.Loooong.Namespace.MyClass. Now COM clients can consume this class using the My.Shorter.ProgID programmatic identier. After registration, the component can be used from any Win32 application. However, theres one more word to be added here. The recommended way of registering a .NET library as a COM component is to assign a strong name to your project, and then install the resulting DLL in the Global Assembly Cache (GAC). In fact, using the command line that Ive just shown assumes that Hello.dll will eventually be added to the GAC. If you really need to deploy the component in its own folder and not place it in the Global Assembly Cache, you have to add the /codebase switch to the command prompt when registering the assembly. This will result in an extra CodeBase entry in the registry, pointing to the correct location of the resulting DLL. You can use this switch for debugging purposes as well. Once the object has been correctly registered in the Windows Registry, you can use it from any COM client. For example, heres how you can consume the Hello.HelloClass component from Visual Basic 6:
Dim obj As Object Set obj = CreateObject(Hello.HelloClass) MsgBox obj.SayHelloTo(Jack) Set obj = Nothing

BUILT-IN .NET TYPE System.SByte (sbyte) System.String (string) System.Int32 (int) System.Int64 (long) System.Byte (byte) System.Int16 (short) System.UInt16 (ushort) System.UInt32 (uint) System.Char (char) System.UInt64 (ulong) System.Boolean (bool) System.Decimal (decimal) System.Single (oat) System.Double (double) System.Object (object)

IDL EQUIVALENT char BSTR long int64 unsigned char short unsigned short unsigned long unsigned short uint64 VARIANT_BOOL wchar_t single double VARIANT

C++ EQUIVALENT char _bstr_t long __int64 unsigned char short unsigned short unsigned long unsigned short unsigned __int64 VARIANT_BOOL DECIMAL oat double _variant_t

Pretty cool, right? While were at it, I can show how you can use the same .NET class from C++. Since theres a little bit more code involved in this case, I invite you to have a look at Listing 1 at the end of this article (to keep things simple, error checking was omitted from the code). You might wonder what customizations can be made at register/unregister time, such as writing additional registry entries or the like. The news is good in this matter, too .NET denes two wonderful method-level attributes: ComRegisterFunctionAttribute and its counterpart ComUnregisterFunctionAttribute. The methods on which they are used will be called whenever the assembly is registered or unregistered:
public class HelloClass { ...code omitted... [ComRegisterFunctionAttribute] public static void RegisterHelloClass(Type t) { MessageBox.Show(Registered type: +

Table 1

12

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Feature

t.Name); } [ComUnregisterFunctionAttribute] public static void UnregisterHelloClass(Type t) { MessageBox.Show(Unregistered type: + t.Name); } }

As shown above, the methods used with the two attributes must be static. You can apply both attributes to the same method, but I dont recommend it unless you have a good reason to do so. There can be more such methods in your code, one for each class that gets exposed to COM, but you cant have more than one registration (or deregistration) method for the same class. I have to add here that all the functionality described so far is also accessible in two other ways. One is to check the Register for COM Interop option in the project properties. Note that the component will be registered as if you were using the /codebase switch in the Assembly Registration Tool. The other method is to do it programmatically. The .NET Framework offers a special class for this

in the System.Runtime.InteropServices namespace called RegistrationServices. This class contains a few useful methods that you might nd interesting, such as the possibility to enumerate all registerable types from an assembly. Isnt it nice when you have such great exibility at hand? Still, theres a catch in all that I described so far. Things work as expected, thats for sure, but have you ever wondered why it works at all? The question seems silly at rst; in fact, this is exactly what those attributes are meant to do, right? Well, yes, but there is also a hidden face you didnt see. Each exposed type will automatically get a few other attributes applied to them. It might help a project if you know a little bit more about this and will eventually lead to better designs. Its time to discuss the more serious stuff. Its time to talk about...

Interfaces, Interfaces Everywhere...


COM is all about implementing well-known or custom interfaces, all of them ultimately deriving from the ubiquitously IUnknown interface with its three methods: QueryInterface, AddRef, and Release. Every .NET type that will eventually be exposed to COM must emulate this somehow,

Shift Your Web Site Into Overdrive.


If you want powerful hosting at record-breaking speeds, go with the Gate.com team. We offer state-of-the-art Windows hosting including ASP.NET 2.0 with free MS SQL servers, powerful VPS solutions, rock-solid dedicated servers, robust Unix hosting, clustered servers, expert 24/7 phone support and much, much more. Get ready to put your Web site in the fast lane.

SHARED HOSTING $ 95 from

/mo

VPS HOSTING 95 $ from

29

/mo

Get your FIRST MONTH FREE* with no contract required.

Visit gate.com/ndj
Call 866.233.0602 and mention referral code 2402
*Offer valid for a limited time. Set-up fee may apply.

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

13

Feature

otherwise the .NET component wont be of any use for COM clients. This is accomplished via ClassInterfaceAttribute, an attribute dened in the System.Runtime. InteropServices namespace that can be applied to your classes. When used, ClassInterfaceAttribute accepts a ClassInterfaceType argument that will establish the type of interface implemented by the class. You can have a dual class interface (ClassInterfaceType.AutoDual), a dispatch-only interface (ClassInterfaceType.AutoDispatch), or a class for which youll implement your own interface (ClassInterfaceType.None). The default value used by .NET when the class is not marked with this attribute is AutoDispatch. This means that the class implements the IDispatch interface and supports late binding COM clients only thats right, type information will not be generated for the exposed class, and its functionality can be discovered by calling IDispatch:: GetIDsOfNames. Class methods will then be callable via IDispatch::Invoke. This is exactly what I did in Listing 1, and this is what happens behind the scenes in the Visual Basic 6 snippet shown earlier. The next enumeration value is AutoDual. This will generate a dual class interface for you. The resulting component can be used both by late binding clients and by early binding COM clients. This might be wonderful, but let me spoil your happiness before its too late: AutoDual has its own problems and its use is not recommended at all for good designs. Remember that the order of methods in a COM interface is very important. You cant reorder the methods inside an interface and live happily ever after, especially when that piece of code is already in production. A dual class interface needs its own dispatch identiers (DISPIDs) for the public stuff dened for that class. This is exactly what AutoDual will do: it will automatically assign unique DISPIDs to your exposed methods, properties, public elds, and so on. Why is this bad news? A project is a living thing, and as it evolves, youll nd yourself redening classes, moving stuff around, and the next thing you know, your DISPIDs are not the same anymore. Other .NET applications are quite happy with that, but this presents a real versioning problem for already existing early binding COM clients, or clients that have cached the initial dispatch identier values. The basic advice here is to avoid using AutoDual at all costs, or to rely on it only if you dont have any other option. However, there might be a solution for this after all: the ace up .NETs sleeve is called DispId (surprise!), which is a nice attribute that can be applied to methods, elds, and properties. Thus, even if you move things around, rest assured noth-

ing will explode. Heres how its used:


public class COMExposedClass { [DispId(1000)] void DoSomething(); }

Voil! You have successfully created an AutoDual class with set dispatch identiers. Lets move on. Whats left for us is ClassInterfaceType.None. No class interface is generated if you use this value, and you need to dene your own interfaces that will then be implemented by the exposed class. This offers the greatest exibility from all values dened by ClassInterfaceType, and is in fact the recommended way of exposing .NET types to COM. When dening interfaces that will later be implemented by a COM-visible .NET component, you do it by giving it a Global Unique Identier (GUID) and specifying the interface type via yet another attribute (InterfaceTypeAttribute):
[Guid(place-a-newly-generated-GUID-here)] [InterfaceType(...a ComInterfaceType enumerated value goes here...)] public class ICoolInterface { ...add some methods... }

Note that the GUID attribute is optional all types in .NET have a GUID associated with them, but if you do want to control this aspect, this is the attribute you need. ComInterfaceType is an enumeration that lets you dene an IUnknown-derivative interface (ComInterfaceType.InterfaceIsIUnknown), an IDispatch-derivative interface (ComInterfaceType.InterfaceIsIDispatch), or a dual interface (ComInterfaceType.InterfaceIsDual), similar to ClassInterface Type. Of course, you can implement non-COM visible interfaces, too (just use ComVisible(false) when dening them, or make them non-public). Obviously, these wont be accessible to COM clients. See Listing 2 for a simple illustration of what Ive just described. It contains a class that implements an IUnknown-type interface, an IDispatch-type interface (I omitted the GUID values from this code), as well as the class that implements them. What if you need to implement a standard COM interface? Youre in luck here, too. All you have to do is rewrite that interface in your solution and add an extra attribute, ComImport; it informs the framework that youre bringing an outside interface into your program. See Listing 3 for a possible denition of the well-known IOleCommandTarget interface (I left the denition for OLECMD and OLECMDTEXT for you as an exercise). Note that here youre forced

14

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Feature

to use a GUID attribute, one whose value must be the same as the one assigned by COM to the original interface. Thats all I need to say about interfaces and classes for now. Now, before moving to more interesting things, I think we have to take another detour and see how .NET types are mapping to COM data types. After all, you need to know what other legacy project will have to deal with when talking with your coolest application ever. I promise to make it short. Itll be only...

a TestTypes class. Now all I have to do is build and register the resulting library (remember our friend, RegAsm.exe?). Once the component is registered, you can use the OleView tool installed with Visual Studio to browse for and look at the generated type library. All I did was apply the methods described in this article.

Exposing Events
Sooner or later you would need to expose some events for your components. Youre already familiar with .NET delegates and events, and youre probably wondering if this knowledge cant be leveraged in the COM world. True enough, this is exactly how COM clients can respond to events happening in your .NET code plus a little help from an intermediate interface. Remember our hello-there class from the beginning of the article? I didnt; its time to add some event-exposing stuff to it. What if the powers that be decide that your class cant accept an empty string as parameter after all, you cant say hello to nobody. An empty string parameter will trigger some kind of error in the code, and this can be done in the form of an HelloError event. To do it right from the COM point of view, the

An Interlude: Mapping of .NET Built-in Types to Win32 Types


Table 1 shows which Interface Denition Language (IDL) type corresponds to which .NET native data type. Everything else (structures, for example) will build upon this table. Remember, IDL is the language used to dene COM interfaces (among others). For good measure, I included C++ types, too (there are slight variations between the two). In fact, there is a very simple way to nd out this correspondence. Check out Listing 4; it denes an ITestTypes interface that contains only dummy methods whose return types are exactly all .NET built-in types. I then implemented this interface in

ISBN 0-9777622-0-3

COPYRIGHT 2007 SYS-CON MEDIA


Visit us at www.dotnetdevelopersjournal.com

from the Worlds Leading i-Technology Publisher

June 2007 Volume: 5 Issue: 6

15

Feature

event will be dened as a method in the intermediate interface that I told you about:
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] public interface IHelloEvents { [DispId(0x00000001)] void HelloError(string error); }

End Sub Private Sub HelloObject_HelloError(ByVal hello_error As String) MsgBox hello_error, vbOKOnly, Error End Sub

Thats it! Youre ready to build full-blown COM components from within Visual Studio and from your framework (and language) of choice.

Note that the interface derives from IDispatch (required when exposing events to COM), and that Ive thrown my own DISPID for the only interface method. HelloError accepts a string parameter this might be useful if the design requires that all errors from HelloClass need to be self-describing. To use this new interface, all I need to do now is to add it as a COM source interface to HelloClass. Heres the new implementation for the hello-there class:
[ComSourceInterfaces(typeof(IHelloEvents))] public class HelloClass : IHello { ...implementation goes here... }

The COM-Friendly User Controls: Exposing ActiveX Functionality from .NET


You might think that once you know all about the COM-related attributes from the .NET Framework, it would be very simple to implement an ActiveX control. After all, whats in an ActiveX control? You need to implement this and that interface and depending on what functionalities your control needs to support, you might want to add some events and youre ready to go. Indeed, the UserControl class that sits at the base of every control dened by you ultimately implements a series of standard COM interfaces that are pretty much mandatory for an ActiveX control: IOleControl, IOleWindow, IViewObject, and so on it really looks like all the hard work has already been done for you. Not so fast. Although Microsoft has given a lot of thought to the interoperability game, ActiveX has somehow disappeared from the list. Yes, this scenario is not supported at all, and, until recently, you were on your own if you needed to build such a beauty. Up to a (fragile) point, this really could be done, but there was no guarantee that things wouldnt start behaving erratically exactly when you were ready to uncork the champagne. I just lied to you: there was in fact just one single scenario available to you. If you really like to play with a hot potato, you could develop ActiveX controls hosted inside Internet Explorer 5.01 and later. There are many things to be considered here, all depending on your project needs security issues, deployment, and so on. If all went ne, your UserControl objects could be brought up on an HTML page via the well-known OBJECT tag. The IE hosting scenario for .NET controls is quite a big, complex subject, and I wont go further on this road. As an aside, what I just said was true for .NET 1.0; starting with 1.1, the framework lets you use Windows Forms from Visual C++ projects using Microsoft Foundation Classes (MFC), plus Internet Explorer 5.01 or later. I said that you couldnt create regular ActiveX controls. Well, things have changed lately. The new kid in town is called Microsoft Interop Forms Toolkit, now in its second release, and available from http://msdn2.microsoft.com/en-us/vbasic/ bb419144.aspx.

Note again that the class has been changed a little bit from what I wrote at the beginning of the article. See Listing 5 for all the implementation details. Ive created an IHello interface, dropped the useless _name data member, and then implemented the IHello interface via HelloClass. Theres a reason why I derived my IHello interface from IDispatch in this listing; I want to show you how to use HelloClass from the old Visual Basic 6, and VB6 cant consume IUnknown objects. Of course, making IHello an AutoDual interface will do just ne, too. The VB6 demo program will look pretty much like the one shown below. I assume youre dealing with an application containing one single form with two buttons called HelloJack and HelloNobody. Clicking on either button will call the SayHelloTo method from HelloClass. Dont forget to add a reference to the COM object you have just created.
Dim WithEvents HelloObject As HelloClass Private Sub Form_Load() Set HelloObject = New HelloClass End Sub Private Sub Form_Unload(Cancel As Integer) Set HelloObject = Nothing End Sub Private Sub HelloJack_Click() MsgBox HelloObject.SayHelloTo Jack, vbOKOnly, Hello End Sub Private Sub HelloNobody_Click() HelloObject.SayHelloTo

16

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Feature Feature Feature Feature

This is a free add-in for Visual Studio (actually, for Visual Basic 2005) that will help you create window forms and user controls ready to be used from Visual Basic 6. You read it perfectly: ofcially, the toolkit supports only VB.NET-to-VB6 scenarios. In its rst release (September 2006), the Interop Toolkit allowed VB6 applications to display .NET Windows Forms easily. Exposing UserControls as ActiveX components was still in beta at that time, but the last release (May 2007) adds full support for creating such controls from VB.NET, too. Once the toolkit is installed, youll end up with two additional project templates for VB.NET: one for building user controls that are visible from VB6 (the InteropUserControl template) and one to allow VB6 programs to use a Windows Form (the VB6 InteropForm template). There is also a very good documentation with plenty of tutorials, as well as a series of sample projects. Since the toolkit already comes with all that, I wont insist on this subject more than is necessary. I leave it to you to explore the powerful toys contained in this kit. I have to say a few more things here. Although the toolkit is intended to work with VB6 projects, you can still use the resulting ActiveX controls from any COM client. An ActiveX control is an ActiveX

control no matter what; be advised though that the toolkit has been created with VB6 in mind, and you should test your project carefully before going live with it. Its been a long ride and now were going for the home stretch. Im sure there is enough here to make you interested in exploring other .NET-COM interoperability issues. For now, I have to summarize everything up in a nice...

Conclusion
Ive only touched the tip of the iceberg in this article about the .NET and COM playground. There are still the issues of deployment to discuss, or what happens with classes that inherit from ComVisible ones, and which are exposed themselves to COM. Also, there are some issues when having overloaded methods; for example, if you have SomeMethod defined twice, what you get is a COM class whose second method is renamed to SomeMethod_2; this is a real problem for VB6, which doesnt recognize methods with underscores in their names. Anyway, your toolbox now contains one more secret when dealing with legacy applications. This can be the beginning of a beautiful friendship between you and those old projects.

LISTING 1: CALLING A .NET COMPONENT FROM C++ // Initialize the COM system ::CoInitialize(NULL); // Retrieve the CLSID for Hello.HelloClass CLSID clsidHello; LPOLESTR szHello = LHello.HelloClass; ::CLSIDFromProgID(szHello, &clsidHello); // Create the IDispatch instance LPDISPATCH lpDispHello = NULL; ::CoCreateInstance( clsidHello, NULL, CLSCTX_INPROC_SERVER, IID_IDispatch, (LPVOID*)&lpDispHello); // Retrive the DISPID for the SayHelloTo method LPOLESTR szHelloFn = LSayHelloTo; DISPID dispSayHelloTo; lpDispHello->GetIDsOfNames( IID_NULL, &szHelloFn, 1, LOCALE_SYSTEM_DEFAULT, &dispSayHelloTo); // Prepare the parameters for the SayHelloTo method DISPPARAMS dispparams = { NULL, NULL, 0, 0 }; dispparams.cArgs = 1; dispparams.rgvarg = new VARIANT[dispparams.cArgs]; dispparams.cNamedArgs = 0; dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[0]. bstrVal = ::SysAllocString(OLESTR(J ack));

// The result will be stored here VARIANT vtRes; ::VariantInit(&vtRes); // Invoke the method lpDispHello->Invoke( dispSayHelloTo, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &vtRes, NULL, NULL); // Here vtRes.bstrVal is equal to Hello there, Jack // Get rid of our component lpDispHello->Release(); // Dont forget to uninitialize COM ::CoUninitialize(); LISTING 2: A CLASSINTERFACETYPE.NONE CLASS IMPLEMENTING TWO INTERFACES using System.Runtime.InteropServices; namespace Test { /// <summary> /// Dene an interface derived from IUnknown /// </summary> [Guid(...), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IInterfaceOne { void TestMethodOne(); } /// <summary> /// Throw in another interface, derived from IDispatch...

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

17

Feature

/// </summary> [Guid(...), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IInterfaceTwo { void TestMethodTwo(); } /// <summary> /// And yet another interface, this time an internal one. /// Even if it is dened with InterfaceIsIUnknown, it /// will not be exposed to COM, because it is internal. /// </summary> [Guid(...), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface INotExposedInterface { void SomeMethod(); } /// <summary> /// This class is dened as ClassInterfaceType.None, and /// implements our interfaces. All members dened by /// these interfaces (except INotExposedInterface, of course) /// are exposed to COM clients. All other methods dened in /// this class are NOT exposed at all, even if they are public. /// That is, ClassInterfaceType.None is used for a class /// that will expose only ComVisible interfaces to the outside /// world. All other methods and members are not exposed. /// </summary> [ClassInterface(ClassInterfaceType.None)] public class TestClass : IInterfaceOne, IInterfaceTwo, INotExposedInterface { // This method IS exposed, because it comes // from the IInterfaceOne interface public void TestMethodOne() {} // This method IS exposed, because it comes // from the IInterfaceTwo interface public int TestMethodTwo() {} // This method, even if implements the method // with the same name from INotExposedInterface // will not be exposed because that interface // is marked as internal. public void SomeMethod() {} // This method IS NOT exposed, because it is // dened by a class marked as ClassInterfaceType.None, // and doesnt come from a ComVisible interface. public void InvisibleMethod() {} } } LISTING 3: THE DEFINITION FOR THE IOLECOMMANDTARGET STANDARD INTERFACE [ComVisible(true), ComImport()] [Guid(B722BCCB-4E68-101B-A2BC-00AA00404770)]

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface IOleCommandTarget { [PreserveSig()] int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] OLECMD[] prgCmds, ref OLECMDTEXT pCmdText); [PreserveSig()] int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdExecOpt, ref object pvaIn, ref object pvaOut); } LISTING 4: CLASS USED TO DISCOVER THE CORRESPONDENCE BETWEEN .NET TYPES AND COM TYPES using System.Runtime.InteropServices; namespace Test { [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public interface ITestTypes { string GetAString(); int GetAnInt(); long GetALong(); sbyte GetSByte(); byte GetByte(); short GetShort(); ushort GetUShort(); uint GetUInt(); char GetChar(); ulong GetULong(); bool GetBool(); decimal GetDecimal(); oat GetFloat(); double GetDouble(); object GetAnObject(); } [ClassInterface(ClassInterfaceType.None)] public class TestTypes : ITestTypes { public string GetAString() { return string.Empty; } public int GetAnInt() { return Int32.MaxValue; } public long GetALong() { return Int64.MaxValue; } public sbyte GetSByte() { return SByte.MaxValue; } public byte GetByte() { return Byte.MaxValue; } public short GetShort() { return short.MaxValue; } public ushort GetUShort() { return ushort.MaxValue; } public uint GetUInt() { return uint.MaxValue; } public char GetChar() { return char.MaxValue; } public ulong GetULong() { return ulong.MaxValue; } public bool GetBool() { return true; } public decimal GetDecimal() { return decimal.MaxValue; } public oat GetFloat() { return oat.MaxValue; } public double GetDouble() { return double.MaxValue; } public object GetAnObject() { return null; } } } LISTING 5: EXPOSE EVENTS TO COM

18

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

using System.Runtime.InteropServices; namespace Test { public delegate void HelloErrorHandler(string error); [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface IHello { string SayHelloTo(string name); } [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] public interface IHelloEvents { [DispId(0x00000001)] void HelloError(string error); } [ClassInterface(ClassInterfaceType.None)] [ComSourceInterfaces(typeof(IHelloEvents))] public class HelloClass : IHello { event HelloErrorHandler HelloError; public string SayHelloTo(string name) { string realName = name.Trim(); if (realName != string.Empty) { return Hello there, + realName; } if (HelloError != null) { HelloError(No one out there?); } return string.Empty; } } }

Advertiser Index
ADVERTISER
Active Endpoints

URL
active-endpoints.com/soa

PHONE

PG
5

ESRI

www.esri.com/develop

1-888-288-1277

Gate.com

www.gate.com/ndj

866-233-0602

13

Microsoft

defyallchallenges.com

36

Real-World AJAX Book

www.realworldajaxbook.com

201-802-3020

15

Real-World Java Seminar

www.realworldjava.com

201-802-3020

29

Software FX

www.softwarefx.com

561-999-8888

35

Verio

www.verio.com/dotnetdoneright

888-Verio33

General Conditions: The Publisher reserves the right to refuse any advertising not meeting the standards that are set to protect the high editorial quality of .Net Developers Journal. All advertising is subject to approval by the Publisher. The Publisher assumes no liability for any costs or damages incurred if for any reason the Publisher fails to publish an advertisement. In no event shall the Publisher be liable for any costs or damages in excess of the cost of the advertisement as a result of a mistake in the advertisement or for any other reason. The Advertiser is fully responsible for all financial liability and terms of the contract executed by the agents or agencies who are acting on behalf of the Advertiser. Conditions set in this document (except the rates) are subject to change by the Publisher without notice. No conditions other than those set forth in this General Conditions Document shall be binding upon the Publisher. Advertisers (and their agencies) are fully responsible for the content of their advertisements printed in .Net Developers Journal. Advertisements are to be printed at the discretion of the Publisher. This discretion includes the positioning of the advertisement, except for preferred positions described in the rate table. Cancellations and changes to advertisements must be made in writing before the closing date. Publisher in this General Conditions Document refers to SYS-CON Publications, Inc. This index is provided as an additional service to our readers. The publisher does not assume any liability for errors or omissions.

Active Endpoints ActiveBPEL


It also includes APIs to access and control the inner workings of the BPEL engine. The J2EE and .NET integrated versions add multi-level load balancing and server fault tolerance and deep integration into each of the supported servers: .NET, WebSphere, JBoss, and WebLogic. In the .NET version, BPEL processes can be launched via either the IIS server API, or a native .NET API. Access security can be handled by either IIS or ASP .NET-based security, allowing access list to be stored in a database, Active Directory, or third-party security suites. Also, ActiveBPEL can be installed in an IIS Web farm where a wide range of load balancing strategies can be used. The WebSphere version can run as a WS-I compliant framework, or inside WebSphere containers to take advantage of advanced

continued from 34

WebSphere capabilities. Security can be handled by the Tivoli Access manager or by legacy or third-party security solutions. ActiveBPEL also supports all of the WebSphere clustering capabilities including automatic failover and content-based routing. On JBoss, ActiveBPEL uses the Axis JAX-RPC for Web services invocations and message exchange, and also allows direct control by the JBoss WS4EE or EJB bindings. It also integrates with JBossSX security or LDAP servers. ActiveBPEL supports all of the JBoss clustering technology, including JGroups and the Tomcat HttpSession object to provide load balancing, failover, and single sign-on. Truly the people at Active Endpoints understand how important it is to use and integrate with the different platforms they run on.

Future
The difference between BPEL and workow is that BPEL focuses on automating Web services, where workow focuses on connecting people-oriented processes. Active Endpoints has recently released ActiveBPEL for People, which extends BPEL to include people-oriented tasks, including roles and Web-based user interfaces.

More Information
For more information, checkout Active Endpoints Web site at http://www.active-endpoints.com/, there is a nice slide show at http://www.bptrends.com/publicationles/BPEL4ProgArchies.pdf, and Wikipedia has a page on BPEL at http:// en.wikipedia.org/wiki/BPEL.

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

19

Feature

Using XML with Stored Procedures Effectively in SQL Server 2005


How things changed
By Srinivas K. Surampalli

About the Author


Srinivas K. Surampalli alias Kal is a senior consultant at Magenic Technologies. He has over a decade of experience in delivering innovative technology solutions to businesses using Microsoft Technologies. He is a MCSD for Microsoft.NET and Microsoft Visual Studio 6.0. When not working, Kal enjoys playing with his two children Avyay and Anagha. kals@magenic.com

ET lets us easily serialize an object into XML and deserialize XML into its corresponding . object. This functionality has been available since .NET 1.0. The introduction of new data type called XML in SQL Server 2005 gives us even more advantages that come in handy with Stored Procedures that attempt to insert/update records in multiple but related tables. Usually this involves passing a huge number of parameters that make up the individual objects to the Stored Procedure; but with SQL Server 2005 we could potentially serialize the object(s) into an XML string and pass it as the input parameter, leaving us with cleaner code thats easy to read and maintain. Consider the case of having a Person object with list of Address and Phone objects. The corresponding database schema would have tables for Person, Address, and Phone. When a new Person is inserted into the database (along with the corresponding address and phone number), generally wed have to pass all the member variables of the Person object to a Stored Procedure and, if successful, call other Stored Procedures to insert the records into the Address and Phone tables. I propose an alternate way of doing the same thing thats a lot simpler. Suppose we serialize the Person object along with its list of Address and Phone objects into XML; this XML parameter would then be passed to the Stored Procedure which could handle the inserts to the Person, Address, and Phone tables all by itself; and within

the Stored Procedure, wed use XQuery against the passed XML to extract the desired data. This eliminates multiple parameters to the Stored Procedure and minimizes the Stored Procedure calls to the database.

XML Serialization Primer


XML Serialization is the process of converting an objects public Properties and Fields (the current state of the object) into an XML stream; likewise, XML deserialization is the process of creating an object from an XML to a state as specied by the XML stream. XML serialization comes in two avors when an object is serialized, its properties could either be expressed as Xml-Elements (default) or as XmlAttributes in the XML output. Consider a simple class called Book as shown in Listing 1. Code to serialize an instance of Book into XML and deserialize the XML into another instance of Book is shown in Listing 2. Serialized XML for an instance of Book is shown in both formats in Listing 3. Note that you would need a default constructor for the class for the XmlSerializer to serialize and deserialize. Also note that for an instance of an object, only those properties are serialized that have a non-null value. The XmlSerializer class (dened in the namespace System.Xml.Serialization) is used for serialization and deserialization processes. As you can see, Xml-Element model is verbose when

20

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Feature Feature Feature Feature

compared to the XmlAttribute model. In this article well consider the XmlAttribute model. As youve seen, to get the output in Xml-Element format, we dont have to do anything since its the default behavior; on the other hand, for the XmlAttribute model, we have to annotate the public properties and public elds with XmlAttributes. To control the generated XML, we could apply special XML Attributes to the class or the public elds and public properties. Attributes provide additional information to the XmlSerializer class on how the data appears in XML. Here are a few important attributes to consider: XmlAttributeAttribute: XmlAttributeAttribute (or just XmlAttribute) should be applied to the public properties and elds of the class that you want to be serialized as attributes (as opposed to elements). This article recommends using attributes model for XML serialization. Listing 1 below already offers an idea of how one model compares to the other. It shows how XmlAttribute is declared for properties. The name given for the attribute is the same as we see in the XML output. Generally wed have the same name as the Property itself. However, if desired, we could change this as shown in Listing 5 for the class Patient for the property MRN that would be serialized as Medical_Record_Number. Note: When specifying an Attribute, the phrase Attribute could be omitted from the name of the attribute since .NET would recognize the attribute. This is true for all Attributes and not just XML attributes. This is the reason why XmlAttributeAttribute can be specied as simply XmlAttribute. XmlRootAttribute: XmlRootAttribute (or just XmlRoot) decorates the class itself. It species how the root element would be displayed in XML. If this isnt specied, the name of the class would appear as-is, as the root element. Usually we dont have to set this attribute. However, there may be a case where the class name may not be suitable as the root name in the serialized XML. This can happen when the class name differs from the name used in business parlance. For instance, lets say we have a class called Doctor; by default, this would appear as the root in the serialized XML. But, if users like to see the root name as Physician instead of Doctor we need to set the XmlRoot attribute for the class as Physician. This is shown in Listing 4. XmlArrayItemAttribute: When we have to serialize a collection XmlArrayItemAttribute comes in handy. Consider the case of a Doctor having a list of patients as a Property. Listing 5 shows the Patient class and a property in the Doctor class called Patients, which is decorated with the attribute XmlArrayItemAttribute. Note the XML output that lists the XML node as Patients (the same name as

that of the property) and each patient is listed with the XML node Patient that is the same as the class name for the Patient class. Weve seen the snippet of code that does serialization and deserialization in Listing 3. However this code snippet is specic to the Book class. To generalize the code for serialization and deserialization, we could dene these functions in a common base class. Lets dene a class called BusinessBase that could serve as a base class to all business objects. This class could then have two functions ToXML() for serialization and FromXML() for deserialization. This way, any business object like Person, Doctor, Patient, Phone, Address, etc. could automatically leverage this functionality. Code for BusinessBase is shown in Listing 6. Notice the Copy() function that serves as a Copy Constructor (deep copy). This is needed since the Deserialize() function of XmlSerializer returns an object that has to be copied into this again. Use of these functions is shown in Listing 7, assuming that weve derived the class Book from BusinessBase. Weve already dened the Person class. Lets add collections to this class, one for the address list and the other for the phone list. To do this, we need two classes called Address and Phone. Listing 8 below shows the Address and Phone classes along with the part of the Person class that reects the change. Note that we have dened enums AddressType and PhoneType to enumerate different types of addresses and phones respectively. These enums have to be kept in synch with the data dened in the database tables AddressType and PhoneType. XmlIgnoreAttribute: Weve already said that by default, all public properties and elds of a class are serialized; if any of these have to be omitted, we need to annotate the desired properties/elds with the attribute XmlIgnoreAttribute or in short, XmlIgnore. Omitting certain data from serialization could

Figure 1: Database schema for Person with Address and Phone information

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

21

Feature

prove handy if you want to serialize only a select set of properties. As seen in Listing 8, we have two properties for the eld _addrType: AddrType and AddressTypeId; the former an AddressType type and latter of int type. In the XML output, we need the property to be expressed as int since thats how its expressed in the database (AddressType table). The other property need not be serialized to XML, which is why its annotated with the attribute XmlIgnore. The corresponding database schema for these classes would be as shown in Figure 1. Now, to insert a Person record along with its Address records and Phone records in one Stored Procedure all we have to do is serialize the Person object and pass the XML to the Stored Procedure. In the Stored Procedure, wed use XQuery to parse the XML and extract the data as needed. Listing 9 shows the code snippet that instantiates a Person object along with its Address and Phone collections, and the corresponding XML representation of the Person instance. The Stored Procedure CreatePerson could be seen in Listing 10. Notice the use of TRY-CATCH blocks in the Stored Procedure. From SQL Server 2005 onwards, this is the preferred way to catch any errors during the execution of a Stored Procedure; in earlier versions, global parameter @@ERROR was used to retrieve the code of the last error when executing a SQL statement, and GO TO statements had to be used to modify the control ow. Using TRY-CATCH block eliminates this to write cleaner code; if an exception does get raised in a TRY block during the execution, control automatically jumps to the CATCH block where the appropriate error-handling code could be placed. If Stored Procedure executes
LISTING 1 DEFINITION OF A BOOK CLASS FOR BOTH FORMATS Book denition for Xml-Elements model Book denition for XmlAttributes model Notice how each property in the class is decorated with XmlAttribute. public class Book { /// <summary> /// title of the book /// </summary> private string _title; public string Title { get { return _title; } set { _title = value; } } /// <summary> /// ISBN of the book /// </summary> private string _isbn; public string ISBN { get { return _isbn; } set { _isbn = value; } } /// <summary>

without any exceptions then control doesnt enter the CATCH block at all. As seen in Listing 10, at the end of the TRY block, we commit the transaction and return the error code (0=success) and the ID of the inserted Person record; whereas, in the CATCH block, we roll back the transaction and return the error code and error message. XQuery or XML Query Language is a W3C specication on how XML could be queried. XQuery specication document could be found at http:// www.w3.org/TR/xquery/. The value() and nodes() are the XML data type methods in SQL Server 2005. Other methods available, but not used in this article are query(), exist(), and modify(). The nodes() method returns an unnamed rowset, which is why its aliased in CreatePerson SP as seen in Listing 10. The value() method extracts the value of the node from the rowset with the type specied. Note that it can extract multiple values if the rowset returned by the nodes() method contains multiple values. This is how multiple address or phone records could be inserted with just one INSERT statement. As youve seen in this article, using XML saves us from passing multiple parameters to the Stored Procedure, and it facilitates inserting records into multiple/linked tables cleanly. Later, if the object properties change or tables get modied, the input to the Stored Procedure doesnt have to change, and could continue to be XML; the Stored Procedure itself would have to be changed to reect the changes, but the caller wouldnt have to modify the input parameters to the Stored Procedure. So code maintenance is easier since minimal changes have to be made.
/// Author of the Book /// </summary> private string _author; public string Author { get { return _author; } set { _author = value; } } /// <summary> /// Default constructor: Needed for Xml serialization /// </summary> public Book() { } /// constructor public Book(string title, string isbn, string author) { Title = title; ISBN = isbn; Author = author; } } public class Book {

22

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Feature Feature Feature Feature

/// <summary> /// title of the book /// </summary> private string _title; [XmlAttribute(Title)] public string Title { get { return _title; } set { _title = value; } } /// <summary> /// ISBN of the book /// </summary> private string _isbn; [XmlAttribute(ISBN)] public string ISBN { get { return _isbn; } set { _isbn = value; } } /// <summary> /// Author of the Book /// </summary> private string _author; [XmlAttribute(Author)] public string Author { get { return _author; } set { _author = value; } } /// <summary> /// Default constructor: Needed for Xml serialization /// </summary> public Book() { } /// constructor public Book(string title, string isbn, string author) { Title = title; ISBN = isbn; Author = author; } } LISTING 2 CODE TO SERIALIZE AND DESERIALIZE Book book = new Book(Visual C# 2005 A Developers Notebook, 0-596-00799-X, Jess Liberty); XmlSerializer serializer = new XmlSerializer(book.GetType()); MemoryStream memoryStream = new MemoryStream(); XmlTextWriter writer = new XmlTextWriter(memoryS tream, Encoding.UTF8); serializer.Serialize(writer, book); // get the stream from the writer memoryStream = writer.BaseStream as MemoryStream; // apply encoding to the stream UTF8Encoding encoding = new UTF8Encoding(); string xml = encoding.GetString(memoryStream. ToArray()).Trim();

// Following code would deserialize xml into Book object // get the byte stream from the string in utf-8 encoding Byte[] bytes = encoding.GetBytes(xml); // create a stream from the bytes memoryStream = new MemoryStream(bytes); Book deserializedBook = serializer.Deserialize(m emoryStream) as Book; //Deserialization LISTING 3 SERIALIZED XML FOR AN INSTANCE OF A BOOK CLASS XML-Element format XML-Attribute format <?xml version=1.0 encoding=utf-8 ?> <Book xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance xmlns:xsd=http://www. w3.org/2001/XMLSchema> <Title>Visual C# 2005 A Developers Notebook</Title> <ISBN>0-596-00799-X</ISBN> <Author>Jesse Liberty</Author> </Book> <?xml version=1.0 encoding=utf-8 ?> <Book xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd=http://www.w3.org/2001/XMLSchema Title=Visual C# 2005 A Developers Notebook ISBN=0-596-00799-X Author=Jesse Liberty /> LISTING 4 DEFINITION OF A DOCTOR CLASS AND A PATIENT CLASS Class denition for Doctor Class denition for Patient [XmlRoot(Physician)] public class Doctor : Person { private string _specialization; [XmlAttribute(Specialization)] public string Specialization { get { return _specialization; } set { _specialization = value;}} private List<Patient> _patients = new List<Patient>(); [XmlArrayItem(typeof(Patient))] public List<Patient> Patients { get { return _patients; } set { _patients = value;}} /// <summary> /// default constrcutor /// </summary> public Doctor(){} // constructor public Doctor ( string fName, string lName, string specialization ) : base(fName, lName) { Specialization = specialization; } // function to add a patient public void AddPatient(Patient patient)

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

23

Feature

{ Patients.Add(patient); } //function to remove a patient public void RemovePatient(Patient pat) { Patients.Remove(pat); } } public class Patient : Person { private string _mrn; [XmlAttribute(Medical_Record_Number)] public string MRN { get { return _mrn; } set { _mrn = value; } } private string _diagnosis; [XmlAttribute(Diagnosis)] public string Diagnosis { get { return _diagnosis; } set { _diagnosis = value; } } /// <summary> /// default constructor /// </summary> public Patient(){} //constructor public Patient ( string fName, string lName ) : base(fName, lName) { } } LISTING 5 SERIALIZED XML FOR AN INSTANCE OF A DOCTOR CLASS <?xml version=1.0 encoding=utf-8 ?> <Physician xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance xmlns: xsd=http://www.w3.org/2001/XMLSchema FirstName=Austin LastName=Szubryt Specializa tion=Paediatrics> <Patients> <Patient FirstName=Malcom LastName=Speed Medical_Record_Number=M555678999 Diagnosis=Burns /> <Patient FirstName=Amit LastName=Jain Medical_Record_Number=M333875452 Diagnosis=Shortness of breath /> </Patients> </Physician> LISTING 6 BUSINESSBASE CLASS DEFINITION using System; using System.IO; using System.Reection; using System.Text; using System.Xml;

using System.Xml.Serialization; namespace DNDJ_XmlSerialization { public class BusinessBase { /// <summary> /// function to copy all properties from bBaseToCopy into this(Copy Constructor) /// </summary> /// <param name=bBaseToCopy></param> public virtual void Copy(BusinessBase bBaseToCopy) { // get all the properties from this PropertyInfo[] properties = this.GetType(). GetProperties(); // loop through the properties foreach (PropertyInfo property in properties) { if (property.CanWrite && property.CanRead) // if there is a Set & Get Method { //Invoke the set on this and get on bBaseToCopy property.GetSetMethod().Invoke(this, new object[] { property.GetGetMethod().Invoke(bBaseToCopy, null) }); } } } /// <summary> /// Function to convert this into XML string. Could be overridden in derived class /// if desired. we use UTF-8 encoding since SqlServer would understand only UTF-8 /// </summary> /// <returns></returns> public virtual string ToXML() { // create a memory stream/text writer for UTF-8 encoding (1-char 1-byte) MemoryStream memoryStream = new MemoryStream(); XmlTextWriter writer = new XmlTextWriter(mem oryStream, Encoding.UTF8); // create the serializer and serialize this XmlSerializer serializer = new XmlSerializer(this.GetType()); serializer.Serialize(writer, this); // get the stream from the writer memoryStream = writer.BaseStream as MemoryStream; // apply encoding to the stream and return the string UTF8Encoding encoding = new UTF8Encoding(); string xml = encoding.GetString(memoryStream. ToArray()).Trim(); // strip out the rst line; we do not need it. xml = xml.Replace(<?xml version=\1.0\

24

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

encoding=\utf-8\?>, string.Empty); return xml; } /// De-serializer: Hydrates an object from the xml; We use UTF-8 encoding since /// SqlServer would understand UTF-8 format only. /// </summary> /// <param name=xml></param> /// <returns></returns> public void FromXML(string xml) { // get the byte stream from the string in utf-8 encoding UTF8Encoding encoding = new UTF8Encoding(); Byte[] bytes = encoding.GetBytes(xml); // create a stream from the bytes MemoryStream memoryStream = new MemoryStream(bytes); // create a serializer and de-serialize this XmlSerializer serializer = new XmlSerializer(this.GetType()); // nally call copy constructor to copy the deserialized output into this this.Copy(serializer.Deserialize(memoryStream) as BusinessBase); } } } LISTING 7 USING TOXML() AND FROMXML() FUNCTIONS Book book = new Book(Visual C# 2005 A Developers Notebook, 0-596-00799-X, Jesse Liberty); string xml = book.ToXML(); // Serialization Book deserializedBook = new Book(); book.FromXML(xml); //Deserialization

LISTING 8 DEFINITION OF THE ADDRESS AND PHONE CLASSES WITH A PARTIAL DEFINITION OF PERSON CLASS Denition of AddressType and Address Denition of PhoneType and Phone public enum AddressType { Home = 1, Work = 2 } public class Address : BusinessBase { private int _addressId = -1; // default [XmlAttribute(AddressId)] public int AddessId { get { return _addressId; } set { _addressId = value;}} private string _line1; [XmlAttribute(Line1)] public string Line1 { get { return _line1; } set { _line1 = value;}} private string _line2; [XmlAttribute(Line2)] public string Line2 { get { return _line2; }

REPRINT IT!
June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

25

Feature

set { _line2 = value;}} private string _city; [XmlAttribute(City)] public string City { get { return _city; } set { _city = value;}} private string _state; [XmlAttribute(State)] public string State { get { return _state; } set { _state = value;}} private string _zipCode; [XmlAttribute(ZipCode)] public string ZipCode { get { return _zipCode; } set { _zipCode = value;}} private AddressType _addrType; [XmlIgnore()] public AddressType AddrType { get { return _addrType; } set { _addrType = value;}} [XmlAttribute(AddressTypeId)] public int AddressTypeId { get { return Convert.ToInt32(_addrType); } set {_addrType = (AddressType)value;} } // default constructor public Address() { } } public enum PhoneType { Home = 1, Work = 2, Mobile = 3, } public class Phone : BusinessBase { private int _phoneId = -1; // default [XmlAttribute(PhoneId)] public int PhoneId { get { return _phoneId; } set { _phoneId = value;}} private int _areaCode; [XmlAttribute(AreaCode)] public int AreaCode { get { return _areaCode; } set { _areaCode = value;}} private int _phoneNumber; [XmlAttribute(PhoneNumber)] public int PhoneNumner { get { return _phoneNumber; } set { _phoneNumber = value;}} private PhoneType _phType; [XmlIgnore()] public PhoneType PhType { get { return _phType; } set { _phType = value;}} [XmlAttribute(PhoneTypeId)]

public PhoneType PhoneTypeId { get { return _phType; } set { _phType = value;}} // default constructor public Phone() {} } public class Person : BusinessBase { private int _personId = -1; // default [XmlAttribute(PersonId)] public int PersonId { get { return _personId; } set { _personId = value;}} private List<Address> _addresses = new List<Address>(); [XmlArrayItem(typeof(Address))] public List<Address> Addresses { get { return _addresses; } set { _addresses = value;}} private List<Phone> _phones = new List<Phone>(); [XmlArrayItem(typeof(Phone))] public List<Phone> Phones { get { return _phones; } set { _phones = value;}} ...} LISTING 9 SERIALIZING PERSON WITH ADDRESSES AND PHONES Person person = new Person(John, Doe); Address addr1 = new Address(); addr1.Line1 = 124 Highland Ave.; addr1.Line2 = Suite 234; addr1.City = Lombard; addr1.State = IL; addr1.ZipCode = 60148; addr1.AddrType = AddressType.Work; person.AddAddress(addr1); Address addr2 = new Address(); addr2.Line1 = 3456 Main St.; addr2.City = Naperville; addr2.State = IL; addr2.ZipCode = 60563; addr2.AddrType = AddressType.Home; person.AddAddress(addr2); Phone phone1 = new Phone(); phone1.AreaCode = 800; phone1.PhoneNumner = 1234567; phone1.PhType = PhoneType.Work; person.AddPhone(phone1); Phone phone2 = new Phone(); phone2.AreaCode = 630; phone2.PhoneNumner = 5481234; phone2.PhType = PhoneType.Home; person.AddPhone(phone2); string xml = person.ToXML(); <Person xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance xmlns:xsd=http://

26

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Feature Feature Feature Feature

www.w3.org/2001/XMLSchema PersonId=-1 FirstName=John LastName=Doe> <Addresses> <Address AddressId=-1 Line1=124 Highland Ave. Line2=Suite 234 City=Lombard State=IL ZipCode=60148 AddressTypeId=2 /> <Address AddressId=-1 Line1=3456 Main St. City=Naperville State=IL ZipCode=60563 AddressTypeId=1 /> </Addresses> <Phones> <Phone PhoneId=-1 AreaCode=800 PhoneNumber=1234567 PhoneTypeId=2 /> <Phone PhoneId=-1 AreaCode=630 PhoneNumber=5481234 PhoneTypeId=1 /> </Phones> </Person> LISTING 10 DEFINITION OF STORED PROCEDURE CREATEPERSON set ANSI_NULLS ON set QUOTED_IDENTIFIER ON go -- sample xml of Person object /*** <Person xmlns:xsi=http://www.w3.org/2001/ XMLSchema-instance xmlns:xsd=http://www. w3.org/2001/XMLSchema PersonId=-1 FirstName=John LastName=Doe> <Addresses> <Address AddressId=-1 Line1=124 Highland Ave. Line2=Suite 234 City=Lombard State=IL ZipCode=60148 AddressTypeId=2 /> <Address AddressId=-1 Line1=3456 Main St. City=Naperville State=IL ZipCode=60563 AddressTypeId=1 /> </Addresses> <Phones> <Phone PhoneId=-1 AreaCode=800 PhoneNumber=1234567 PhoneTypeId=2 /> <Phone PhoneId=-1 AreaCode=630 PhoneNumber=5481234 PhoneTypeId=1 /> </Phones> </Person> ***/ CREATE PROCEDURE [dbo].[CreatePerson] ( @personXML XML ) AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; DECLARE @personId INT -- saves the value of the inserted Person records Id BEGIN TRY --begin the TRY block -- wrap the multiple inserts in a Transaction BEGIN TRANSACTION CREATE_PERSON -- insert the new person record INSERT INTO dbo.Person(FirstName, LastName)

SELECT newPerson.value(@FirstName, varchar(50)) FirstName, newPerson.value(@LastName, varchar(50)) LastName FROM @personXML.nodes(/Person) node(newPerson) SET @personId = @@IDENTITY -- note down the Id of the inserted record -- insert records into Address table (multiple possible) INSERT INTO dbo.Address(PersonId, AddressTypeId, Line1, Line2, City, State, Zipcode) SELECT @personId, newAddr.value(@AddressTypeId, int) AddressTypeId, newAddr.value(@Line1, varchar(50)) Line1, newAddr.value(@Line2, varchar(50)) Line2, newAddr.value(@City, varchar(50)) City, newAddr.value(@State, varchar(50)) State, newAddr.value(@ZipCode, int) Zipcode FROM @personXML.nodes(/Person/Addresses/Address) node(newAddr) -- insert records into Phone table (multiple possible) INSERT INTO dbo.Phone(PersonId, PhoneTypeId, AreaCode, PhoneNumber) SELECT @personId, newPhone.value(@PhoneTypeId, int) PhoneTypeId, newPhone.value(@AreaCode, int) AreaCode, newPhone.value(@PhoneNumber, int) PhoneNumber FROM @personXML.nodes(/Person/Phones/ Phone) node(newPhone) -- now that all inserts have been completed, commit the transaction COMMIT TRANSACTION CREATE_PERSON -- return the error code followed by the Id of the inserted Person record SELECT ISNULL(ERROR_NUMBER(), 0) ErrorCode, @personId END TRY -- end of TRY block BEGIN CATCH -- control would come here only in case of an exception -- since an exception has ocurred, rollback the transaction IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION CREATE_PERSON -- error has ocurred; return error number and err_msg to the caller SELECT ISNULL(ERROR_NUMBER(), 0) ErrorCode, ERROR_MESSAGE() ErrorMessage END CATCH -- end of CATCH block END -- end of SP

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

27

Indexed LINQ

Indexed LINQ
By Aaron Erickson

Optimizing the performance of LINQ queries using in-memory indexes

anguage Integrated Query (LINQ) is a very powerful new technology coming to us with Visual Studio 2008. There is a great deal of innovation going on in the LINQ space, including innovative projects like LINQ-to-Flickr and LINQto-Amazon (among many others), in addition to the great things Microsoft is providing in LINQ-to-SQL and LINQ-to-XML. This article dives deeper into the more mundane details about how to optimally query data you already have in memory via LINQ-to-Objects. LINQ lets you do queries on any object (typically collections or arrays) that support IEnumerable<T>. For example, if you have an online bookstore, you might decide to cache your products in memory on the Web server to improve performance. In such a case, you might have a List<Books> bookCache that you query this way when someone goes to look up a book:
var searchResult = from b in bookCache where b.ISBN=978-1590596326 select b;

Indexing to the Rescue


Nearly any searching algorithm is better than sequential search. Of course, to use any of those algorithms, we have to know a little bit more about what we are searching. If your items are in order, you might use a binary search, which would help quite a bit. However, for the best possible performance, you want your search to use an index just like every relational database worth mentioning does when it searches through a large collection of rows.

Implementation of a Generic Index


As it turns out, we need three elements to be able to do this in a generic manner: An ability to easily specify which elds in a class we are going to have an indexed collection of that we are going to index. A mechanism that allows for building and maintaining these indexes automatically. Modication of the implementation of Where so that it takes advantage of the index, rather than doing a sequential search.

Now, imagine your cache of books for your Web site contains, say, a million books, and you need to make this scale in a serious way. In such a case, you might be interested in optimizing how LINQ works.

The [Indexable()] Attribute


If were going to have an easy-to-use indexing mechanism, we need a simple way to answer the what question as in what is it we are going to build indexes on. Building indexes on every property in a class is almost certainly going to be too much causing performance lags when you build the collection, not to mention wasting memory if we build indexes on elds that arent searched on. To be more specic about what we are going to index, we need to add some meta-information to our class. Thats exactly what Attributes are for. Thankfully, for a simple indexing implementation, we need nothing more than to mark certain properties as indexable. The implementation of the attribute is very simple:
public class IndexableAttribute : Attribute { }

How LINQ Queries in Memory Objects


About the Author
Aaron Erickson is a principal consultant for Magenic. He is a ruthless advocate for concentrating on creating the most business value in the least amount of time when his clients entrust him to deliver a technical solution. Aaron has been delivering solutions on the Microsoft platform for over 14 years, and currently leads open source development efforts related to LINQ, including Indexes of Objects (i4o) and LINQ to Expressions (MetaLinq). aarone@magenic.com

When you use LINQ syntax to do a query, what really happens is that the syntax is converted to a series of method calls, such as where, join, and others related to the query operators that LINQ supports. Of interest to us is, for purposes of a query, how Microsoft implements Where in LINQ to Objects. Given that Where has to work for any IEnumerable<T>, theres very little that the default implementation can do to optimize the query. As a result, the default search over IEnumerable<T> in LINQ is a sequential search. In other words, if you are searching for the book at the bottom of a million-book pile, your search may not be terribly fast.

Once in place, we can do the following with any

28

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com


Sponsored by

Produced by

For this and other great events visit www.EVENTS.SYS-CON.com

COPYRIGHT 2007 SYS-CON MEDIA

The Java Coffee Cup logo and Sun logos are trademarks of Sun Microsystems June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

29

Indexed LINQ

class we intend to use in an indexed collection:


class Book { private private private private

string _isbn; string _authorFirstName; string _authorLastName; int _id;

associates them with the appropriate inner list. For example, the inner list might hold everything that starts with A. The outer list might hold 26 items, one for each letter (a list for A, a list for B, and so forth). When you look up an item, you would look up by its rst letter, nd the list to look in, and then search through the smaller list, rather than searching through everything. Our data structure for a particular index looks like this:
Dictionary<int, List<T>>

[Indexable()] public string ISBN { get { return _isbn; } set { _isbn = value; } } [Indexable()] public int Id { get { return _id; } set { _id = value; } } public string AuthorFirstName { get { return _authorFirstName; } set { _authorFirstName = value; } } [Indexable()] public string AuthorLastName { get { return _authorLastName; } set { _authorLastName = value; } } }

In our actual implementation, well look up by int rather than string for the index. We use int because int is what the GetHashCode() method returns from all objects in .NET. One of the keys to making this work is that all objects support GetHashCode() which lets us, while not uniquely identify an object, effectively partition it into very small groups. Of course, there may be more than one index in our collection. So in actuality, our implementation will use a Dictionary of indexes, which gives us the following structure in the end:
protected Dictionary<string, Dictionary<int, List<T>>> _indexes = new Dictionary<string, Dictionary<int, List<T>>>();

Implementation of IndexableCollection<T>
The next step is to have a collection that, while based on the collection functionality that .NET gives us, lets us intercept certain operations on it that are of interest to maintaining an index. Namely, we want, at the very least, to know when something is added or removed from the collection. To start, well derive from Collection<T>, from System.Collections.ObjectModel, as follows:
public class IndexableCollection<T> : Collection<T> { }

Building the indexes on the construction of the collection is the next step. When we build a new IndexableCollection<T>, we need to reect what were collecting, nd out which properties are going to be indexed (by looking for the presence of the IndexableAttribute), and create the index accordingly. The following routine, called from the constructor, accomplishes that goal:
protected void BuildIndexes() { PropertyInfo[] allProps = typeof(T).GetProperties(); foreach (PropertyInfo prop in allProps) { object[] attributes = prop.GetCustomAt tributes(true); foreach (object attribute in attributes) if (attribute is IndexableAttribute) _indexes.Add(prop.Name, new Dictionary<int, List<T>>()); } }

Build the Index


The rst thing we need is a structure to hold our indexes. An index can be dened as a sort of dictionary of lists. The inner list holds everything that relates to a certain indexed value. The outer dictionary holds all the different indexed values and

30

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ

Intercept Collection Adds


Once indexes are built, you need a mechanism to add data to the indexes (and eventually remove it). In our implementation, were going to redene the Add method in our collection:
public new void Add(T newItem) { foreach(string key in _indexes.Keys) { PropertyInfo theProp = typeof(T). GetProperty(key); if (theProp != null) { int hashCode = theProp. GetValue(newItem, null).GetHashCode(); Dictionary<int, List<T>> index = _indexes[key]; if (index.ContainsKey(hashCode)) index[hashCode].Add(newItem); else { List<T> newList = new List<T>(1); newList.Add(newItem); index.Add(hashCode, newList); } } } base.Add(newItem); }

We need to make these public since anything that might want to implement an index on this is going to need to be able to see if a property has an index and retrieve the index if a given index exists.

A Smarter Where Implementation


At this point, we have an indexed collection that could, in theory, be used by a query processor to enable faster searches based on lookups rather than searching sequentially (a k a associative search). Which brings us to our next problem how do we write our own implementation of Where? The answer is a new C# 3.0 feature called Extension Methods. While explaining how extension methods work is a topic worthy of its own article, for purposes of this exercise, we can say that extension methods let us extend classes we dont own to do things we want them to do. Extending our own class to have its own custom where implementation, as it turns out, isnt terribly difcult. To do so, you create a static class, with a static Where method that contains a special signature:
public static class IndexableCollectionExtention { //extend the where when we are working with indexable collections! public static IEnumerable<T> Where<T> ( this IndexableCollection<T> sourceCollection, Expression<Func<T, bool>> expr )

The mechanics of Add are to iterate through the known indexes, and using reection, retrieve the hash code of the appropriate property value for each eld on the object were going to index. Once we have that value (hashCode above), we look in the index to see if we have an entry yet for that hash code. If we do, we simple add the item to the list of entries that have the given hash code. If not, we create a new entry in that index based on the hash code. Once were done, we call the Add routine in the base class so that we have the normal behavior of add implemented. We can now go ahead and add a couple of helper methods that would help anyone trying to implement an index:
public bool PropertyHasIndex(string propName) { return _indexes.ContainsKey(propName); } public Dictionary<int, List<T>> GetIndexByProp erty(string propName) { return _indexes[propName]; }

Extension methods extend the class you specify in the rst parameter, the one shown here with the modier on the parameter. The second parameter is the actual parameter that a Where method is always passed namely, the expression were going to evaluate to determine whether we do an index lookup or simply pass the call through to the

Figure 1:

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

31

Indexed LINQ

default implementation of where.

Analyzing the Expression Tree


Expressions are a representation of code as data something very familiar to those from the LISP world. The Where method takes an Expression as its parameter, allowing the implementer of where to determine how a search is going to take place. In LINQ-to-SQL this is where the expression is converted to SQL code that is, in turn, passed to the database. However, in our case, rather than do a conversion, were simply going to examine the tree to determine if we can use an index, and if so, gure out what to look up. Two conditions must be true for us to be able to use an index. The where clause must be testing for equality The property on the left-hand side of the comparison must be a eld we happen to have an index for.

BinaryExpression binExp = (BinaryExpression)expr.Body;

Examine the Left Side


Once we know were dealing with a binary equals expression in the body of the lambda, we can determine whether were dealing with a eld that is indexable. We pass the left side of our binary expression to the following method:
private static bool HasIndexablePropertyOnLef t<T>( Expression leftSide, IndexableCollection<T> sourceCollection) { if (leftSide.NodeType == ExpressionType. MemberAccess) return sourceCollection.PropertyHasIndex( ((MemberExpression)leftSide).Member.Name ); else return false; }

Is Where Testing for Equality?


Our test to determine whether the programmer is doing an equality operation is not as complex as it might seem. A Where always gets a certain type of expression passed to it called a LambdaExpression. LambdaExpressions are simply delegates assigned to a data structure. In our case the LambdaExpression tends to be a specialized version that always returns a Boolean value by virtue of being part of a where clause. Lambda expressions have two main properties a set of Parameters that reect the parameters that go into the lambda, and a Body that represents what the lambda actually does (see Figure 1). Of particular interest to us is the Body expression. Specically, we want to make sure that: a. The expression is, in fact, a BinaryExpression b. And that the BinaryExpression represents an equals operation The following code does both for us:
if (expr.Body.NodeType == ExpressionType.Equal) { //Equality is a binary expression

This routine performs two important tests. The rst is to see if its the kind of expression we can use on the left side something called a MemberExpression. MemberExpressions are expressions that relate to any property lookup that might occur in code. We need the expression to be a member expression because thats the only type that will let us determine which index, if any, to use. The second test, which only applies if it is, in fact, a MemberExpression, is to make sure that the property that the MemberExpression tries to access is, in fact, indexed by the collection were working with. To perform this test, we use the PropertyHasIndex method we wrote earlier, passing the name of the property the user passed into the query. If this works, we know we have a field for which an indexed lookup is possible.

Evaluate the Right Side


Now that we know what index to use, we need to

As anyone who has ever done work with databases knows, indexes can provide dramatic performance improvements for large sets of data.
32
June 2007 Volume: 5 Issue: 6 Visit us at www.dotnetdevelopersjournal.com

Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ Indexed LINQ

gure out the value that well be looking up in the index. So we need to gure out what value the right side of the BinaryExpression (a k a the right side of the equals) evaluates to. Unlike the left side, were less picky about what might be on the right side of the expression. The right side could be a constant number, a string, or any other valid C# statement that evaluates to something. The following routine does the evaluation:
private static int? GetHashRight(Expression rightSide) { //rightside is where we get our hash... switch (rightSide.NodeType) { //shortcut constants, dont eval case ExpressionType.Constant: ConstantExpression constExp = (ConstantExpression)rightSide; return (constExp.Value.GetHashCode()); default: LambdaExpression evalRight = Ex.Lambda(rightSide, null); //Compile it, invoke it, and get the resulting hash return evalRight.Compile(). DynamicInvoke(null). GetHashCode(); } }

foreach (T item in result) yield return item; } noIndex = false; //we found an index

The process of doing an indexed lookup is a matter of calling Where again, this time, on a much smaller set the items within a given page of the index rather than the entire enumeration. The results of the where are then iterated through, using the yield return statement on each item as you would with any other iteration routine. In case no index is found, you simply do the following:
if (noIndex) //no index? just do it the normal slow way then... { IEnumerable<T> sourceEnum = sourceCollection. AsEnumerable<T>(); IEnumerable<T> result = sourceEnum. Where<T>(expr.Compile()); foreach (T resultItem in result) yield return resultItem; }

This does nothing more than return results just like the default where implementation does looking at each element of the collection and checking to see if it passed the test in the expression passed to the Where method.

For constant nodes which could be an int, string, or some other data structure, we go ahead and simply grab the value, get its hash, and return that. For other nodes, we convert the expression to a LambdaExpression, compile it, invoke the resulting method, and get our hash from the result. Either way, so long as the result isnt null, well get an int lookup value we can use for our index lookup.

Results
As anyone who has ever done work with databases knows, indexes can provide dramatic performance improvements for large sets of data. Over a large set of a million books, our lookup time would have been around 1,200ms for the unfortunate book at the end of the collection. Using an index, however, brings that time down to less than 10ms a 100x performance improvement. But its not without a cost. The code to maintain an index in cases where the values of the objects change more frequently is somewhat more complex, requiring you to implement a change notication in the class youll be indexing. And just like database indexes, adding indexes slows down your add, remove, and update operations making the developer who has domain knowledge of how the objects will be used the only person who can possibly know the best indexing strategy. The source code that ties all of this together is available at www.codeplex.com/i4o, an open source project designed to make indexed collections available to all developers on the .NET platform.

Do the Query
Once we have a property we know we have an index and a value to look up in the index, we can go ahead and process the query using the index:
hashRight = GetHashRight(rightSide); Dictionary<int, List<T>> myIndex = sourceCollection.GetIndexByProperty(prope rty); if (myIndex.ContainsKey(hashRight.Value)) { IEnumerable<T> sourceEnum = myIndex[hashRight. Value].AsEnumerable<T>(); IEnumerable<T> result = sourceEnum. Where<T>(expr.Compile());

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

33

Product Review

Active Endpoints ActiveBPEL


Using and integrating different platforms
By Dennis Hayes

TM

What Is BPEL?
BPEL or Business Process Execution Language is an XML and Web standardsbased SOA (service-oriented architecture) standard that allows business people to combine services into automated processes. As described in this review, Active Endpoints ActiveBPEL product family includes a visual designer that works by allowing nonprogrammers to assemble Web services into processes by dragging and dropping graphical representations of components (Web services) and wiring them together in sequences and owcharts (see Figure 1). There are ve parts of a BPEL application. The true BPEL part is an XML le that conforms to the BPEL standard (BPEL4WS 1.1 and WS-BPEL 2.0). This le contains the denition of the activities that make up the BPEL process. Another part are the services that get integrated by BPEL; these are dened by standard WSDL les. The other three parts are the proprietary BPEL engine that runs the processes, the proprietary graphic designer that functions as the IDE or the development environment that allows drag n drop design of processes, and the nal part is the administrative tools for deploying and monitoring the BPEL process. BPEL is considered programming in the large because it deals with higher-level constructs such as ow control and looping, processes that are long running (for instance, a request for a loan approval might be routed to the accounting department and sit there for days before being approved or denied), fault tolerance (it can be set up to start an alarm process if accounting does not respond to the loan request within a week), and it uses compensation instead of transactions (a transaction cant be kept open for the length of time needed in a long-running process, so, if a stage of a process fails, it has to be xed, not just rolled back).

Figure 2:

Active Endpoints and ActiveBPEL


About the Author
Dennis Hayes is a programmer at Georgia Tech in Atlanta, Georgia, where he writes software for the Adult Cognition Lab in the Psychology Department. He has been involved with the Mono project for over ve years. dennisdotnet@yahoo.com

Active Endpoints has three versions of their BPEL products: a standalone open source version, a standalone commercial version that can run under either .NET or Tomcat, and an integrated commercial version that can run under

Figure 1:

J2EE or .NET. Active Endpoints provides a complete set of administrative tools and a sophisticated development environment based on the Eclipse IDE (see Figure 2). Active Endpoints created the open source ActiveBPEL Engine to generate interest in the BPEL standard and to help educate the community about BPEL. The open source ActiveBPEL Engine is uses the same source code as used in the ActiveBPEL commercial products. The open source version is recommended for design and testing, but not for deployment because it lacks the system integration, clustering support, and security that are included in the commercial versions and typically needed in enterprise-level BPEL implementations. The open source version does contain everything needed to create and run BPEL applications without these enterprise-level features, including the designer and execution engine, management console, debugging, static BPEL analysis, multiple languages (XPath, XQuery, JavaScript, and custom languages), persistence, and all the documentation. The open source version is released under the GPL and is maintained by Active Endpoints. Active Endpoints also provides free forum-based support and premium support on a fee-based subscription basis. The commercial standalone (paid license and support) version runs on Tomcat or .NET, and adds support for WS-Security, WS-Reliable Messaging, advance process ltering and alerts, suspend and retry via both the console and APIs, enhanced process deployment and invocation, and process versioning (currently running processes use the old version, new instances of a process use the new version, with the old version being retired after all running processes end). continued on page 19

34

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

Visit us at www.dotnetdevelopersjournal.com

June 2007 Volume: 5 Issue: 6

35

36
Your challenge: create rich, dynamic PC or mobile apps. Defy it: deliver value, not just TM data with Visual Studio and Windows Vista. More tips and tools at defyallchallenges.com

June 2007 Volume: 5 Issue: 6

Visit us at www.dotnetdevelopersjournal.com

S-ar putea să vă placă și