A primitive, generic DataLayer using Generics
I've only just starting dabbling with Generics and although I can see how cool they are I can also see that it will take some time to get used to them. To me it seems that although I know what they are I'm not sure that I always use it when I should - or how I should for that matter!
Today I started tinkering with some Whidbey code and decided to try and write a DataAccessLayer which used Generics. It started off super-well. I created a small abstract base class named BaseClass and then inherited from it to create SpecializedClass. Normally I would now go about the arduous task of creating a wrapper collection class to return strongly typed instances but with Generics that's not necessary. In my DataLayer class I can return collections of SpecializedClass's like so:
public static ICollection<SpecializedClass> GetSpecializedClassByCategory(int categoryId)
{...}
So, as you can see I've removed the need to create (possibly) dozens of strongly typed collection classes - NEAT!
After that I thought that I'd see if I could take it a bit further. I decided that I'd create a Data Helper class which could take a DataTable and return strongly typed collections; that way I could expand the above code to something like this:
public static ICollection<SpecializedClass> GetSpecializedClassByCategory(int categoryId)
{
adapter.Fill( dt ) ;
return ConvertTableToCollection<SpecializedClass>( dt );
}
The possibilites here looked good... if only I can get that ConvertTableToCollection method to work then I can use it to create all of my strongly typed collections! Now this is about the time that it started to get tricky for me. On paper this is what I had:
private static Collection<T> ConvertTableToCollection<T>(DataTable dt)
where T : BaseClass
{
Collection<T> items = new Collection<T>();
foreach(DataRow row in dt.Rows)
{
}
return items;
}
As you can see I really wasn't sure exactly how I could create the actual instances. What would have been ideal is if Generics allowed me to create a constructor constraint which took typed args such as this:
private static Collection<T> ConvertTableToCollection<T>(DataTable dt)
where T : Item, new( DataRow )
Then I could simply change the above method to look like this:
private static Collection<T> ConvertTableToCollection<T>(DataTable dt)
where T : BaseClass, new(DataRow)
{
Collection<T> items = new Collection<T>();
foreach(DataRow row in dt.Rows)
{
items.Add( new T( row ) ) ;
}
return items;
}
Unfortunately you can't do that. What I ended up doing was to create an interface named IApplicationObject with a Create(DataRow r) member on it and then add the interface constraint to the Generic method. The only problem here is that, because my interface returns BaseClass items I need to cast the return of IApplicationObject.Create to a T after the call. Here's the final code snippet:
private static Collection<T> ConvertTableToCollection<T>(DataTable dt)
where T : Item, IApplicationObject, new()
{
Collection<T> items = new Collection<T>() ;
foreach(DataRow row in dt.Rows)
{
T item = new T() ;
items.Add((T)item.Create(row)) ;
}
return items;
}
That code is so neat because now *all* of my DataLayer calls which return collections look the same internally......
public static ICollection<A> GetAs(...)
public static ICollection<B> GetBs(...)
public static ICollection<C> GetCs(...)
public static ICollection<D> GetDs(...)
... you just fill a data table and pass it to a 10 line Generic method!
Recommended Reading:An Introduction to C# Generics