Av rating:
Total votes: 16
Total comments: 3


Amirthalingam Prasanna
Using partial classes to make intelligent datasets
13 July 2005

The dataset is an integral part of Microsoft’s new data access model ADO.NET. It introduces a simple offline method for retrieving and updating data. Before using the dataset, you should have an understanding of the Microsoft .NET platform and a basic understanding of the dataset model.

Key concepts in datasets

The dataset is an in-memory representation of a subset of the database. It can have tables, rows, constraints and relationships between tables. Though the dataset is capable of handling relational data efficiently, it fails if you try to extend its capabilities.

In good object-oriented design, the data and the methods used on the data should be encapsulated. But with the dataset in .NET 1.0 and 1.1, users had to pass the dataset to business components to perform certain functions. There are a few limitations to this strategy, since the data and the methods that work on the data are not encapsulated as a single, logical unit.

Before looking at extending the capabilities of the .NET 2.0 dataset with business functionality, it is important to look at typed datasets.

Typed datasets can be strongly typed with table and column names to make them less error-prone and to provide a simple syntax for accessing data. Typed datasets create an auto-generated class inherited from the dataset base class. With typed datasets, the syntax for accessing data is simplified compared to untyped datasets. For example:

Untyped dataset syntax (C#):

DataSet ds = new DataSet();
//load the data into the dataset
string s = ds.Tables[”InvoiceHeader”].Rows[0][”InvoiceId”].ToString();

Typed dataset syntax (C#):

Invoice ds = new Invoice();
//load the data into the dataset
string s = ds.InvoiceHeader[0].InvoiceId;

The typed dataset has a very simple syntax for accessing data. The column and table names are properties of the typed dataset. In the untyped dataset, this information has to be passed as an index or string, making it more error-prone. If you misspell the table name, for example, the compiler will not catch the error, but at runtime your program will fail.

Although the typed dataset makes dataset functions easy to use, it is difficult to add business functions into it. Let’s take a simple scenario in which we have a dataset that resembles an invoice with two datatables, one for the invoice header and one for invoice details. The invoice header contains fields such as invoice id, customer name, mailing address and contact number. The invoice detail contains invoice id, item code, unit price and ordered quantity. This simple structure is mapped into a typed dataset as shown in Figure 1:

Figure 1 Typed Invoice Dataset

The typed dataset is ideal for representing the structure of the invoice, but what if we want to add functionality to it? Let’s say we want to apply a 10 percent discount to any invoice detail line in which the subtotal is greater that $1,000, and calculate the resulting amount. We’ll use partial classes to add that functionality to our typed dataset.

Partial classes

Partial classes are classes defined across one or more source files. These multiple definitions are put into one class by the compiler. Partial classes are extremely useful when two or more people with different concerns need to work on a class definition.

In our example, the typed dataset we create will generate a few partial classes to hold the information on the tables and columns. The person who generates the code for the typed dataset is limited to generating the source for the relational representation of our invoice. The other person, the business developer, focuses on adding business functionality to the invoice class.

Implementing business-aware datasets with typed datasets

Each typed dataset created will generate a partial class for the dataset, a partial nested class for each table in the dataset (with the name <table name>DataTable), a partial nested class to map a row for each table in the dataset (with the name <table name>Row). The code below shows the partial class definitions generated for the typed dataset in Figure 1.

C# Code

[System.Serializable()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.ComponentModel.ToolboxItem(true)]
[System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedDataSetSchema")]
[System.Xml.Serialization.XmlRootAttribute("Invoice")]
[System.ComponentModel.Design.HelpKeywordAttribute("vs.data.DataSet")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly")]
public partial class Invoice : System.Data.DataSet
{
//More code here
//...
[System.Serializable()]
[System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedTableSchema")]
public partial class InvoiceHeaderDataTable : System.Data.DataTable, System.Collections.IEnumerable
{
//More code here
//...
}
[System.Serializable()]
[System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedTableSchema")]
public partial class InvoiceDetailDataTable : System.Data.DataTable, System.Collections.IEnumerable
{
//More code here
//...
}
public partial class InvoiceHeaderRow : System.Data.DataRow
{
//More code here
//...
}
public partial class InvoiceDetailRow : System.Data.DataRow
{
//More code here
//...
}
//More code here
//...
}

When you analyze the code, you can see there is a partial class generated for the dataset named Invoice inheriting from DataSet. So another partial class definition for the Invoice class can be created to add more functionality at the dataset level. Similarly, you can add functionality at the data table level by providing partial class definitions to the classes InvoiceHeaderDataTable and InvoiceDetailDataTable, or at the data row level by providing partial class definitions for InvoiceHeaderRow and InvoiceDetailRow.

It’s very important to add the business functionality to the correct partial class and at the correct level. If, for example, you want to calculate the discounted amount of an invoice, it has to be on a row level on the invoice header using a collection of invoice detail rows to calculate the invoice total. So an ideal place to implement this is in the InvoiceHeaderRow class.

C# Code

public partial class Invoice
{
public partial class InvoiceHeaderRow
{
public double DiscountedAmount
{
get
{
double discountedAmt = 0;
foreach (InvoiceDetailRow row in this.GetInvoiceDetailRows())
{
double rowSubTotal = row.UnitPrice * row.OrderedQty;
if (rowSubTotal > 1000)
{
discountedAmt += (rowSubTotal * 90 / 100);
}
else
{
discountedAmt += rowSubTotal;
}
}
return discountedAmt;
}
}
}

Even though we are providing a partial class implementation for InvoiceHeaderRow class, we still need to nest it inside the Invoice partial class definition. When the partial classes generated from the typed dataset and the partial class definitions we created are compiled, the InvoiceHeaderRow objects have a new read-only property called DiscountedAmount that calculates the invoice amount after applying the discount as per the business rules.

But couldn’t we have implemented this property directly inside the generated source, since we have the source for the typed dataset we created? That is not a viable approach because any changes made on the typed dataset will regenerate the code, and handwritten code inside the generated source will be lost.

Although it is possible to implement business functionality into typed datasets by inheriting the auto-generated class from the typed dataset, it might be overkill to have both the base classes and the child classes for each dataset exposed.

Conclusion

Datasets are an important construct used in data-driven applications. This article shows one approach to making your datasets more intelligent by incorporating business functionality using partial classes as part of .NET 2.0.



This article has been viewed 16261 times.
Amirthalingam Prasanna

Author profile: Amirthalingam Prasanna

Prasanna is a software engineer, technical author and trainer with many years of development and consulting experience in the software development industry. He is a Microsoft MVP in the Visual developer category, and a MCPD on enterprise application development. He has authored many articles and has worked on content creation for many Microsoft certification exams and courses. He is also a frequent speaker at Microsoft technology conferences and events. You can read his blog at www.prasanna.ws and e-mail him at feedback@prasanna.ws

Search for other articles by Amirthalingam Prasanna

Rate this article:   Avg rating: from a total of 16 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: Use as displaymember?
Posted by: Anonymous (not signed in)
Posted on: Monday, October 16, 2006 at 7:59 AM
Message: What if you want to use such a calculated property as your 'DiscountedAmount' as a displaymember in a combobox? This doesn't seem to work as expected.

I would like to do this with a calculated 'DisplayName' that should return a formatted string like: 'Name (ID)', but cannot get this to work correctly...

Thanks for your reply!

Subject: Great Stuff!
Posted by: Anonymous (not signed in)
Posted on: Wednesday, October 18, 2006 at 9:22 PM
Message: Perhaps I am a little slow off the mark but then I like to think that I've just been too busy to spend time learning... But...
Finally I have found a use for partial classes that seriously makes sense. I can see a similar scenario around referenced web services.
Thanks

Subject: Problem Serializing
Posted by: Anonymous (not signed in)
Posted on: Monday, February 26, 2007 at 10:38 PM
Message: Hi,
I have added a string property called Test to my Strongly typed Row class. When I pass this over a remoting boundary the added property becomes null. Any ideas why? Seems to not serialize it.

 






recommended site pinvoke

PInvoke.net is a user-driven wiki which provides .NET developers with native method signatures, so they don't have to spend time writing them from scratch.




SmartAssembly: Eating Our Own Dogfood
 Quite often at Red Gate, we are some of our own most enthusiastic software-users. SmartAssembly is a... Read more...

.NET Reflector Pro to the rescue
 Almost all applications have to interface with components or modules written by somebody else, for... Read more...

Has .NET Reflector Saved Your Bacon?
 We think Reflector is a fantastic tool, and we know you do too. We'd love to hear about the times... Read more...

How to build a Query Template Explorer
 Having introduced his cross-platform Query Template solution, Michael now gives us the technical... Read more...

.NET Reflector FAQ (Frequently Asked Questions)
 Bart tackles all the commonly asked questions about .NET Reflector. Each answer is done as a separate... Read more...

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Configuring Forms Authentication in SharePoint 2007
 Damon Armstrong provides a step-by-step guide to the processes, quirks and pitfalls of setting up... Read more...

Over 150,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk