Advanced Basics

Passing Data Between Objects in an Application

Ken Spencer

Code download available at:AdvancedBasics0307.exe(178 KB)

Q What is the best way to pass data between layers in an application? There seems to be a myriad of choices.

Q What is the best way to pass data between layers in an application? There seems to be a myriad of choices.

A Passing data between forms, classes, Web pages, and Web Services is always a challenge. While it may seem quite simple at first, application demands may place different requirements on data at the endpoints and require certain changes to its format.

A Passing data between forms, classes, Web pages, and Web Services is always a challenge. While it may seem quite simple at first, application demands may place different requirements on data at the endpoints and require certain changes to its format.

How many ways can you move data between parts of an operation? Let me take a stab at a short listing:

  • Structures
  • Classes (with serialization and without)
  • Arrays, collections
  • DataSets, DataTables, DataRows
  • XML
  • Shared variables
  • Public properties exposed from classes

There are other methods, but these are some of the common ones. Let's take a couple of these approaches and look at the pros and cons of each. Before you begin, you should think about what you are going to do with the data when it arrives at its destination. If you are going to use it with a DataSet, DataTable, or DataRow, then an easily transferable format is best. That is, a format that directly translates to the desired format, such as a DataRow.

First, let's take a look at structures and classes since they can have properties, methods, and constructors. There are some differences, however, which you can read more about in Moving to VB.NET: Strategies, Concepts, and Code by Dan Appleman (APress, 2003). Now let's take a look at some practical things I've discovered about structures and classes.

Structures are great because they also behave like lightweight classes since they sit on the stack instead of the heap, as long as you use value types. A simple structure looks like this:

Public Structure SomeData Dim Name As String Dim Description As String End Structure

The structure is used like this:

Public MyStuff As SomeData

You can also use and return a structure from a function as shown in the following code snippet:

Public Function DoSomething(ByVal Name As String) As SomeData With MyStuff .Name = Name .Description = "Good stuff" End With Return MyStuff End Function

As you can see, the function loads the structure with data, then returns it from the function.

Now, let's call the function from another application, in fact from two different applications:

Dim oMyClass As New SomeComponent.Class1() Dim MyStuff As SomeComponent.Class1.SomeData MyStuff = oMyClass.DoSomething(GetUserName) txtDescription.Text = MyStuff.Description txtName.Text = MyStuff.Name oMyClass = Nothing

This code returns the name of the current user and the description baked into the class. Now let's call this method from a Web application. This also works just fine. Until, that is, you put the component in COM+. Then you will get an error message stating that the class is not marked for serialization. You can resolve this by adding the serialization attribute to the class, like so:

<Serializable()> _ Public Class Class1

That solves that problem. So far so good. I'm building a multitier application for a client and the application uses structures, arrays, classes, datasets, and more at different points. In one case, I combine both structures and databases by creating a structure that contains a dataset and some other pieces of information.

The following structure can hold a DataTable and a string. You can pass this around with the data from SQL Server™ and with other data as well:

Public Structure CustomerStuff Dim MoreInfo As String Dim dt As DataTable End Structure

The function that returns this structure is shown in Figure 1. RunSQLWithDataSet is in the downloadable sample at the link at the top of this article.

Figure 1 ReturnSomeData

Function ReturnSomedata() As CustomerStuff Dim custstuff As CustomerStuff Dim ds As DataSet Const ConnectionString = _ getTrustedConnection() ds = RunSQLWithDataSet( _ "Select * from customers", ConnectionString) custstuff.dt = ds.Tables(0) custstuff.MoreInfo = "This was really cool" Return custstuff End Function

Using the new method is just like using the other one:

Dim oMyClass As New SomeComponent.Class1() Dim MyCustomer As SomeComponent.Class1.CustomerStuff MyCustomer = oMyClass.ReturnSomedata txtDescription.Text = MyCustomer.MoreInfo DataGrid1.DataSource = MyCustomer.dt oMyClass = Nothing

As you can see, the structure provides a nice way to handle multiple types of data in one place. This solves the problem of passing back data returned from a function that has more than one return type. This approach is simpler than trying to create a class that holds the DataSet along with the other data. It's also simpler than trying to extend the DataSet itself to hold the additional data.

Now for the drawbacks associated with structures. I ran into two different problems. First, structures do not support events. If you try to use events in a structure, nothing happens when you run the app. So, classes rule in this regard. Second, I had a problem with structures and Visual Studio® .NET. I created a data library that contains a function similar to the one you just saw that returns a structure. It works fine when I call it from the client, so I thought it would be safe to put the structure in a separate component that could be referenced by the client and the data library. This would allow both the data library and UI to reference the third component. When I tried this approach, Visual Studio .NET kept telling me to add a reference to the project for the assembly containing the structure. The trouble was I had already added this reference! But the code would not compile. So I ended up putting the structure in the data library and letting both the middle tier and the UI reference it. It works, but not quite as cleanly as I had hoped.

Now, let's take a look at using classes to hold data. It's worth mentioning that both classes and structures can be bound to controls to make your life much easier. This makes it very simple to set up a control once and link it to a datasource for good. Since they are both synchronized, you don't have to worry about updating one or the other. For more information on data binding, see Billy Hollis's article, "Not Your Father's Data Binding".

Let's consider a simple example where you need to work with customer data. You could put this data into either a structure or a class. What's the difference? Well, that depends on what you want to do with the data. If you want to wire the data to controls and make your life easy, then classes are a good choice because they can fire events, as shown in Figure 2.

Figure 2 Using Classes

Public Class Customer Private privateCustomerID As String Private privateCompanyName As String Private privateContactName As String Public Event CustomerIDChanged As EventHandler Public Event CompanyNameChanged As EventHandler Public Event ContactNameChanged As EventHandler Public Property CustomerID() As String Get Return privateCustomerID End Get Set(ByVal Value As String) privateCustomerID = Value RaiseEvent CustomerIDChanged(Me, New EventArgs()) End Set End Property Public Property CompanyName() As String Get Return privateCompanyName End Get Set(ByVal Value As String) privateCompanyName = Value RaiseEvent CompanyNameChanged(Me, New EventArgs()) End Set End Property Public Property ContactName() As String Get Return privateContactName End Get Set(ByVal Value As String) privateContactName = Value RaiseEvent ContactNameChanged(Me, New EventArgs()) End Set End Property Sub Reset() CustomerID = "" CompanyName = "" ContactName = "" End Sub End Class

There are two important features of this simple class. First, the events that signify (such as CustomerIDChanged) are fired whenever a property is modified. The data binding architecture listens for these events and when one fires, it causes the data binding to resynchronize, reloading the controls. Your code just needs to raise the event when you change a property.

This class shows another important feature. In the application I was building, I needed to reset the class and have this action zero out or reset the bound controls. I tried recreating the instance of the class like this:

Dim CurrentCustomer As New Customer()

That didn't work. I finally hit on an elegant solution: a "Reset" method which can be called any time an app needs to refresh the class. Calling Reset will automatically force all bound controls to refresh to the correct state, clearing the form for a new entry.

Now let's turn to DataSets for a minute. I'm going to call the Update method of the SQLDataAdapter and pass it a typed DataSet. No problem, right? Well, not so fast.

Let's look at a sample. This function returns a DataSet with customer data by making a call to the RunSQLWithDataSet function as shown in the following code:

Function RetrieveCustomerContacts() As DataSet Dim ds As DataSet ds = RunSQLWithDataSet("Select CustomerID, " & _ " CompanyName, ContactName from customers", _ ConnectionString, "Customers") Return ds End Function

The last parameter to RunSQLWithDataSet specifies the name of the table returned in the DataSet. This is very important, as you'll see in a moment. The client application uses a typed DataSet, which is loaded via the Form_Load event of the Windows® Form with the following code:

Dim oData As New SomeComponent.Class1() Dim ds As DataSet ds = oData.RetrieveCustomerContacts CustomerContactInfo1.Merge(ds.Tables(0))

The tricky part of this code is the last line, which calls the Merge method of the typed DataSet. This takes the DataSet returned from the function and merges it with the Customers table of the typed DataSet. The trick is that the schema and the name of the untyped table must match or the Merge will not work as intended; the DataSet table will be added to the DataSet as a new table, instead of being merged with the Customers table.

Let's assume you have everything named correctly and it all works as expected. As you continue coding, at some point you need to change the typed DataSet. In my example, I'd like to change it by adding a string field to reflect a new field that exists in the database. The only problem is that the field in the database is an integer. If you run this code you'll get a run-time error on the Merge method. But that problem is caught pretty easily.

What happens if you modify the typed DataSet and then try an update when one of the datatypes is incorrect? You may get an error such as "Input string in incorrect format." I got this error and it drove me nuts because the typed DataSet was complex, having 25 elements or so. The error message doesn't tell you where the error occurred. A Google search for the error string was of no use. I finally found the error by printing the typed DataSets schema and comparing it field-by-field with the tables schema. I found a discrepancy where the DataSet element and the corresponding field in the table did not match, I fixed the typed DataSet and I tried it again. This time it worked.

As mentioned earlier, there are many ways to move data around in an application, but each application's needs may be different. It's a good idea to prototype different parts of your code, working out the kinks of transferring data along the way. Then you can devise standards for different methods while adding to your own knowledge base of potential pitfalls.

Send your questions and comments for Ken to  basics@microsoft.com.

Ken Spencer works for 32X Tech (https://www.32X.com), where he provides training, software development, and consulting services on Microsoft technologies.