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 DataSet
s 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.
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).
[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...
PropertyInfo[] pi = typeof(T).GetProperties();
Type piType;
for (int i = 0; i < pi.Length; i++)
{
piType = pi[i].PropertyType;
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.