When designing an n-tier application, one of the key decisions you make is how to pass information between the tiers of the application. Many Windows DNA applications took advantage of custom objects that mapped to real-world entities. These objects contained scalar data types, arrays, collections, recordsets, and other base objects, which the developer combined to create objects that could persist during calls between different application layers. But .NET provides a native object called the DataSet that can represent multiple tables and their relationships much more efficiently than the custom objects created using Visual Studio 6. In fact, by inheriting from the base DataSet and giving it “intelligence” in the form of properties and methods with specific knowledge of the underlying tables upon which it is based, a typed DataSet becomes the ideal vehicle for representing business entities.
Most developers using typed DataSets agree in theory that they are good repositories for stateful data passing between application tiers. But when they attempt to turn this theoretical understanding of the typed DataSet into a practical implementation, they find out that it’s not as simple as it seems.
Benefits of typed DataSets as business entities
There are two major benefits of using the typed DataSet as a business entity. First, since the typed DataSet resolves all of its internal types at design time, manipulating the in-memory representation of the typed DataSet at runtime is generally faster than manipulating an untyped object. Second, by defining data types and relationships at a much more granular level than you can with an untyped DataSet, you can trap errors at higher levels of an application.
For example, if your typed DataSet defines a CustomerID property as a five-character numeric field (which matches the underlying field in the associated database), the typed DataSet will trap an input violation when the user tries to update it in the presentation layer. With an untyped DataSet, the error wouldn’t be trapped until you attempted to update the underlying database, unless you added code to do this check every time you attempted to use the untyped DataSet. The typed DataSet lets you encapsulate all of the business entity’s behavior in a single object.
How many typed DataSets?
Determining the number of typed DataSets required is one of the first stumbling blocks that designers encounter when using typed DataSets in their applications. For example, if you’re trying to represent a real-world order entry system, do you create one typed DataSet each for customers, orders, invoices, order line items, invoice line items, and products? Or, do you create a single typed DataSet that includes all of these database entities and use relationships in the typed DataSet to tie them together?
Developers who make the argument for creating individual business entities for each database entity defend it by saying that this uses as little memory as possible for the entity and minimizes load times when building the business entity. But this approach negates one of the key advantages of the typed DataSet (i.e., verifying referential integrity between the tables at the highest level of the application).
So how do you get the advantages of using relationships to verify referential integrity while minimizing load times and memory utilization? You create a single typed DataSet and then populate only the portions of the DataSet necessary for the operations you’re performing. In general, you’ll load the DataSet from left to right. For example, if you want to view the Orders and Order Line Items for a single customer and avoid referential integrity errors when loading the typed DataSet, you’ll put a single record in the Customers table, load all of the orders and line items in the Orders and Order Line Items tables, and load into the Product table the products that appear in any of the line items. However, if you just want a list of customers, you can load the Customers table but not load any of the other tables.
Typed DataSets in the data layer
Another common mistake that developers make is putting typed DataSets into the data layer. The typed DataSets allow faster in-memory operations, but operations that update underlying databases are faster when you use untyped DataSets. For this reason, your data access logic components (DALC) in the data layer of the application should only use untyped DataSets. Your business process (BP) components should call the DALCs and then build the typed DataSet using the merge function to load the tables from the untyped DataSets into the business entity represented by the typed DataSet.
To optimize performance in an environment where the business components are on different physical machines than the DALCs, you should never make more than a single call to the DALC component to build a business entity. For example, if you want to fill the Orders and Order Details tables in a business entity (BE), have a DALC function called GetOrdersDetails that builds an untyped DataSet and returns that to the business layer where the typed DataSet is composed.
Exposing data layer objects to the presentation layer
Classic n-tier design requires that any call to the data layer go through the business layer first. Although this is good in theory, it kills performance in practice. As long as the data returned by the data layer is read only and no business processing is required to validate the data, it’s fine to allow the presentation layer to call directly into the data layer. For example, if you need to create selection screens or populate drop-down lists for your user interface, create a set of methods in your DALCs that return data readers. Then your UI designers can simply bind to the data readers to create a responsive interface that’s data driven. By adding data or page caching to the application, you can speed up the rendering even more.
Typed DataSets are great objects to use for effectively implementing BEs. By using these design suggestions, you can mitigate some of the performance and reusability concerns normally associated with their use in production applications.