Click here to Skip to main content
15,860,972 members
Articles / Web Development / ASP.NET
Article

DataTableAdapter

Rate me:
Please Sign up or sign in to vote.
5.00/5 (11 votes)
4 Nov 2006CPOL2 min read 69.6K   376   38   17
How to flatten collections for an ObjectDataSource using the Adapter Pattern.

Introduction

This is a class and example of how to flatten a domain model into a DataTable to ease the use of an ObjectDataSource.

Background

I have been using OR/M modeling for a while now in WinForms applications. Recently, I had the need for an ASP.NET application, and quickly got very frustrated with the ObjectDataSource. I don't want to get into a debate between DataSets and Collections. Suffice it to say, I use strongly typed collections.

Problem

When using a non-flat (hierarchical) domain model, it is impossible to bind to a complex property. For instance, in the Domain Model below, I will need to show the Instructor's FullName in the class list and subsequently be able to edit it. However, while I can bind to the Instructor, it will show whatever is in the Person's toString() method. This is fine for display, but what about editing? I could implement IConvertible<T>, but I don't need this functionality outside of my presentation layer. Therefore, let's use the Adaptor design pattern to adapt a collection of objects (or just a single object) into a DataTable.

This approach also yields a number of other advantages. The ObjectDataSource doesn't sort or page well without a lot of plumbing. When bound to a DataTable, it handles this natively so no extra code needs to be written.

DataStore.png

Using the code

Since Course doesn't have the display properties "InstructorId", "InstructorFullName", or "AvailableSlots", we can essentially mix them in to the data type as if they were a part of the class. Dynamic languages like Ruby and Python allow this on the object level, negating the need to do this in the conversion. C#3 (and VB9) have something called extension methods, but so far no mention has been made of extension properties (which we need for data binding).

C#
[DataObjectMethod(DataObjectMethodType.Select)]
public static DataTable FindAll()
{
    DataTableAdapter<Course> dta = new DataTableAdapter<Course>();
    dta.AddColumn("InstructorId", typeof(string), 
        delegate(Course c) { return c.Instructor.Id; });
    dta.AddColumn("InstructorFullName", typeof(string), 
        delegate(Course c) { return c.Instructor.FullName; });
    dta.AddColumn("AvailableSlots", typeof(string), 
        delegate(Course c) { return c.MaxStudents - c.Students.Count; });
    return dta.GetDataTable(CourseService.FindAll());
}

Potential Issues

Obviously, reflection is being used extensively here. This may cause some performance issues depending on how large an object base is being used.

Source

The full source for the DataTableAdapter is in the App_Code folder under DataTableAdapter.cs. It is well commented and fairly self-explanatory. I would like to point out the part for which I had to search. It has to do with detecting a nullable type and then getting its underlying type. The reason this is necessary is because a DataTable will not accept a nullable type in a column definition. It uses DBNull for this value. Anyways, here is an excerpt...

C#
//Get all the pi's once.
PropertyInfo[] pi = typeof(T).GetProperties();

Type piType;
//Add all the properties as columns
for (int i = 0; i < pi.Length; i++)
{
    piType = pi[i].PropertyType;

    //Data tables don't accept nullables in the column type.  Therefore,
    //when we encounter a nullable, we need to get it's underlying type.
    if (piType.IsGenericType && 
           piType.GetGenericTypeDefinition() == typeof(Nullable<>))
        piType = Nullable.GetUnderlyingType(piType);

    dt.Columns.Add(new DataColumn(pi[i].Name, piType));
}

References

The idea for this came from a Google search that yielded this result at Brendan Tompkin's blog.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
I work at a financial services firm using mostly .NET. I enjoy my job immensely and love teaching, training, and mentoring younger developers.

Comments and Discussions

 
Questiondreamviewer question Pin
SSN4-Jun-08 5:14
SSN4-Jun-08 5:14 
GeneralJust what I've been looking for Pin
waltersm23-Oct-07 22:53
waltersm23-Oct-07 22:53 
GeneralRe: Just what I've been looking for Pin
Jafin26-Dec-07 14:05
Jafin26-Dec-07 14:05 
GeneralGood Job Pin
Marco225020-Sep-07 18:20
Marco225020-Sep-07 18:20 
GeneralCollections Pin
LurkingVariable21-Jan-07 10:43
LurkingVariable21-Jan-07 10:43 
GeneralRe: Collections Pin
Craig G. Wilson21-Jan-07 15:50
Craig G. Wilson21-Jan-07 15:50 
Hi LV,
Thanks for your remarks. I actually have translated this to VB for some coworkers, so I'll post it up here as a seperate download.

As for your other question, I believe the answer is simple. When I use the DataTableAdapter, I don't use it directly. I create a wrapper class for the object I'm attempting to display. This wrapper class can be static (VB: Shared) because it doesn't really do anything but redirect calls to the appropriate domain/infrastructure layers. Hence, this becomes a presentation layer class. For example:

The example above of consisting of the Findall Method would be apart of a larger class. This large adapter class would be the only class with which the ObjectDataSource interacts so that maintenance only occurs in one place, thus seperating more "business like methods" away from the presentation layer.

Public Class CourseAdapter

	Public Shared Function FindAll() As DataTable
		Dim dta As New DataTableAdapter(Of Course)
		
		'add columns here as necessary
		Return dta.GetDataTable(CourseService.FindAll())
	End Function

	Public Shared Function Insert(ByVal description As String, _
					ByVal maxStudents As Integer, _
					ByVal name As String) 'or more as necessary
		
		'Insert the record
	End Function

	Public Shared Function Update(...)
		'Update a record	
	End Function

	Public Function Delete(...)
		'Delete a record		
	End Function
End Class


Hope this is what you were looking for. The tricky part is figuring out which parameters to place into each method. The ObjectDataSource is fairly strict and doesn't always tell you what you need.
GeneralRe: Collections Pin
LurkingVariable21-Jan-07 15:59
LurkingVariable21-Jan-07 15:59 
GeneralData Base Connectivity Pin
Fazal_qureshi17-Dec-06 22:39
Fazal_qureshi17-Dec-06 22:39 
GeneralRe: Data Base Connectivity Pin
Craig G. Wilson18-Dec-06 2:45
Craig G. Wilson18-Dec-06 2:45 
QuestionI receive the error: Parameter count mismatch Pin
klhkdsjhfksjhdfkjshf15-Nov-06 11:53
klhkdsjhfksjhdfkjshf15-Nov-06 11:53 
AnswerRe: I receive the error: Parameter count mismatch Pin
Craig G. Wilson15-Nov-06 12:01
Craig G. Wilson15-Nov-06 12:01 
GeneralRe: I receive the error: Parameter count mismatch Pin
Wilhelm Berg7-Feb-07 10:07
Wilhelm Berg7-Feb-07 10:07 
QuestionSource Pin
Wilhelm Berg7-Nov-06 8:32
Wilhelm Berg7-Nov-06 8:32 
AnswerRe: Source Pin
Craig G. Wilson7-Nov-06 8:35
Craig G. Wilson7-Nov-06 8:35 
AnswerRe: Source Pin
Wilhelm Berg8-Nov-06 6:38
Wilhelm Berg8-Nov-06 6:38 
GeneralRe: Source Pin
Craig G. Wilson8-Nov-06 6:56
Craig G. Wilson8-Nov-06 6:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.