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) { // Fill a DataTable adapter.Fill( dt ) ; // Then return it as a strongly typed class... return ConvertTableToCollection<SpecializedClass>( dt ); }
The possibilites here looked good... if only I can get that ConvertTableToCollection
private static Collection<T> ConvertTableToCollection<T>(DataTable dt) where T : BaseClass { Collection<T> items = new Collection<T>(); foreach(DataRow row in dt.Rows) { // how can I use generics to enforce that T can be // seeded with a DataRow? Something like... // items.Add( T.Create( row ) ) ; // or... // items.Add( new T( row ) ) ; } 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