Although some of Microsoft's marketing materials have presented ADO.NET (the data access layer in the Microsoft .NET Framework) as a simple upgrade to ADO, that's a rather misleading way to look at it. ADO.NET is really an almost completely new architecture for data access. This means that inevitably the developer faces a learning curve when moving from ADO to ADO.NET. But how can you remain productive while working your way up that learning curve?
Fortunately, Microsoft thought about that problem before releasing the .NET Framework. The developers of .NET worked hard to provide interoperability between COM applications (developed with tools such as Visual Basic 6.0 or Visual C++ 6.0) and .NET applications. In this article, I'll review some of the differences between ADO and ADO.NET, and then show you how you can use existing ADO-based COM components from your new ADO.NET-based .NET applications.
From Recordset to DataSet
In ADO through version 2.8 (sometimes called "classic ADO") the basic object for holding a group of related records in an application is the Recordset. A Recordset is, roughly, a single table or view stored in memory. It also has a direct and intimate connection with the original data source. Depending on your cursor settings, a Recordset may retrieve batches of records as you move through the data. Each Recordset has a pointer to a current record, which you can edit. Edits to the current record are saved or discarded before you move to another record. The Recordset and the other classic ADO objects are universal, applying equally well to any type of data.
If you've been working with ADO for any length of time, you've probably memorized all of those Recordset facts, and now assume that this is how data access is supposed to work. Well, in ADO.NET, things are almost completely different. Here are some basic facts about the DataSet, which is the new core data object in ADO.NET:
- A DataSet can represent an entire relational database in memory, complete with tables, relations, and views.
- A DataSet is designed to work without any continuing connection to the original data source.
- Data in a DataSet is bulk-loaded, rather than being loaded on demand.
- There's no concept of cursor types in a DataSet.
- DataSets have no current record pointer You can use For Each loops to move through the data.
- You can store many edits in a DataSet, and write them to the original data source in a single operation.
- Though the DataSet is universal, other objects in ADO.NET come in different versions for different data sources.
Obviously the differences between the Recordset and the DataSet are profound. And yet, if you've been following Microsoft's architectural recommendations, you probably have a sizeable investment in data access layer components that return Recordsets. Do you need to discard that entire investment to move to .NET?
I'm happy to say that the answer to that question is "no". Microsoft has provided ways to draw data from a Recordset into a DataSet. In the next section of this article, I'll show you a simple example of the code to do this.
A Simple COM Server
To demonstrate how this interoperability between COM and .NET works, I'll start with a simple COM server writte in VB 6.0. In fact, it's so simple that it's under ten lines of code:
Public Function GetCustomers(strCountry As String) As Recordset Dim cnn As New ADODB.Connection Dim rst As New ADODB.Recordset cnn.ConnectionString = "Provider=SQLOLEDB;Data Source=(local);" & _ "Initial Catalog=Northwind;Integrated Security=SSPI" cnn.Open rst.Open "SELECT * FROM Customers WHERE Country = '" & strCountry & "'", cnn Set GetCustomers = rst End Function
This code resides in a class named Customers in a project named DataLayer. When you invoke the GetCustomers method of the Customers class with a string specifying the name of a country, it returns a Recordset object containing all of the customers in the country from the SQL Server version of the Northwind sample database. Compiling this code produces a COM server named DataLayer.dll
From COM to .NET
Suppose your existing code uses COM servers similar to DataLayer.dll to return Recordsets with data of interest. How can you use that data in a .NET application? Here's a step-by-step approach to building a .NET client for this COM server.
First, create a new Visual Basic .NET Windows application. I gave my application the uninspired name "Client." Right-click on the References node in Solution Explorer and select Add Reference. In the Add Reference dialog box, shown in Figure 1, select the COM tab. Click the Browse button and browse to the DataLayer.dll file to add it to the Selected Components list. Then click OK to add the reference to the project.
Under the covers, there's quite a lot going on when you add this reference. You'll notice that the ADODB library (containing the classic ADO objects) and your DLL both show up in the References node in Solution Explorer. Visual Studio .NET automatically creates runtime-callable wrappers (RCWs) for the two COM libraries. An RCW is a .NET library which wraps a COM library and presents the same interfaces to your .NET code that the COM component exposes. RCWs are the only mechanism through which a .NET component can call a COM server.
Next, add a DataGrid control to the default form in the project. Switch to code view, and add references to the .NET namespaces containing the data access components:
Imports System.Data Imports System.Data.OleDb
Now, write code to execute when the form is loaded:
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load ' Create an instance of the COM server Dim c As DataLayer.Customers = New DataLayer.Customers() ' Create a DataSet to hold the .NET data Dim ds As DataSet = New DataSet() ' Retrieve a classic ADO recordset from the COM server Dim rs As ADODB.Recordset = c.GetCustomers("France") ' Move the data from the Recordset to the DataSet Dim da As OleDbDataAdapter = New OleDbDataAdapter() da.Fill(ds, rs, "Customers") ' Bind the data to the user interface dgCustomers.DataSource = ds dgCustomers.DataMember = "Customers" End Sub
The comments should make it clear what's going on in this code, but take a moment to appreciate the results. Even though COM and .NET are two very different ways to write applications, the .NET designers went the extra distance to make sure they can be seamlessly connected. In particular, this code instantiates COM components and works with them just like native .NET components. It also uses an overloaded form of the OleDataAdapter.Fill method to shuffle the data from the Recordset to the DataSet. Does it work? Sure! Figure 2 shows the .NET form displaying data from the COM server.
There is much more to COM-.NET interoperability than I can show you in a short example. If you need to move an application from the COM world to the .NET world, I recommend purchasing a copy of Adam Nathan's excellent book .NET AND COM: THE COMPLETE INTEROPERABILITY GUIDE (Sams, 2002).
Enabling Gradual Migration
The interoperability between COM servers and .NET clients is one of the secrets to making the transition from old code to new code. If you're maintaining a large existing code base of VB6 or VC6 components, it's likely that you've divided it into functional clients and servers. In that case, there's no need to do a "big bang" migration of all the components at once. Instead, you can keep your existing COM servers, and write new client components as .NET components (or vice versa; it's also possible for COM clients to use .NET servers). This way you can slide new .NET components into your existing system without overhauling interfaces and without undertaking the risk of a complete rewrite. It's a winning situation all around.
About the Author
Mike Gunderloy is the author of over 20 books and numerous articles on development topics, and the lead developer for Larkware. Check out his MCAD 70-305, MCAD 70-306, and MCAD 70-310 Training Guides from Que Publishing. When he's not writing code, Mike putters in the garden on his farm in eastern Washington state.