In a hand coded data-access layer you often end up with some variant of this pattern when translating a datareader to an entity.
public List<ICarrier> LoadList()
{
List<ICarrier> items = new List<ICarrier>();
using (SafeDataReader reader = new AdoHelper().ExecuteDataReader(CommandType.Text, "Select * from _lu_Carrier"))
{
while (reader.Read())
{
items.Add(MapCarrier(reader));
}
}
return items;
}
where your ADOHelper does whatever it needs to do to connect to the database and run your query. This is generally coupled with "mapping" method (MapCarrier in this instance)
public static ICarrier MapCarrier(SafeDataReader dr)
{
ICarrier carrier = new Carrier();
carrier.Name = dr.GetString("Name");
carrier.Phone = dr.GetString("Phone");
carrier.SCAC = dr.GetString("SCAC");
carrier.URL = dr.GetString("URL");
return carrier;
}
I think this is a fairly standard approach, and it works well. However, it's tedious and can lead to lots of repetition as your DAL grows. Enter the magic of generics and delegates which allow the code in the first block to become something like this:
public List<ICarrier> GetList()
{
return new AdoHelper().ExecuteList<ICarrier>(CommandType.Text,
"Select * from _lu_Carrier",
MapCarrier);
}
ExecuteList<T> is overloaded to allow for a few variations which all route to this:
public List<T> ExecuteList<T>(CommandType commandType, string commandText, Func<T, SafeDataReader> mappingCallback, params SqlParameter[] parameters)
{
List<T> collection = new List<T>();
using (SafeDataReader reader = ExecuteDataReader(commandType, commandText, parameters))
{
while (reader.Read())
{
collection.Add(mappingCallback(reader));
}
}
return collection;
}
The "magic" here is the Func<T SafeDataReader> delegate being passed in. This is (lifted directly from Rhino.Commons) and is declared as
public delegate TResult Func<TResult, S>(S s1);
ExecuteItem<T> is similar, but returns a single entity instead of a collection
public T ExecuteItem<T>(CommandType commandType, string commandText, Func<T, SafeDataReader> mappingCallback, params SqlParameter[] parameters)
{
using (SafeDataReader reader = ExecuteDataReader(commandType, commandText, parameters))
{
while (reader.Read())
{
return mappingCallback(reader);
}
}
return default(T);
}
Over even a small project this can save a lot of typing and a substantial amount of time.