Av rating:
Total votes: 10
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 10432 times.
Amirthalingam Prasanna

Author profile: Amirthalingam Prasanna

Prasanna is a software engineer, technical author and trainer with over 7 years experience in the software development industry. He is a Microsoft MVP in the Visual developer category, a MCT and a MCPD on enterprise application development. 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 10 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.





Damon Armstrong
Customizing the Login Page in SharePoint 2007
 Damon shows how a few simple steps lead you to being able to include the login form in a consistent look and feel to...  Read more...


ANTS Profiler and the Un-Rest Cure
 After a while, successful applications can get set in their ways. Bart Read and Andrew Hunter decided... Read more...

Silverlight-Speed Loop
 John Bower steps up a gear, produces a Lamborghini, and examines the process of using a high-speed... Read more...

Sid: Vicious
 Dan Archer documents his epic struggle with an apparently simple task of authenticating user... Read more...

Embedding Help so it will be used
 It is not good enough to make assumptions about the way that users go about getting help when they use... Read more...

Optimising a High-Performance Computing Tool
 Many computer systems nowadays have their ‘correctness’ checked using sample testing, but this isn't... 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...

.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...

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...

Beginning ASP.NET 2.0
 It seems that there is both excitement and confusion surrounding Master Pages and Themes. A big part of... 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