The family has been battling the flu for the past week, but it appears the worse is now over. Today was a day of recuperation, drinking green tea and orange Gatorade to replenish the body's nutrients.
Although a bit delirious from lack of sleep, I was playing a bit with the elegance of generics and factories in the data access layer. A lot of this realization was due to my recent investigation of the Data Access Guidance Package in the Web Services Software Factory. Although there are shortcomings in their repository implementations, I give them extra points for their elegant use of generics and the use of factories to reinforce high cohesion and the single-responsibility principle.
The nice bit of refactoring Microsoft has done with the repositories is similar to what Sean has done with Codus. I haven't played with Codus in awhile, but I remember being impressed with how well it was refactored. If you are looking for a gentle introduction to O/R Mapping, Codus is a great way to get started and offers a good productivity boost.
Data Access Objects
Tossing the Data Access Guidance Package aside for a moment, I decided to mimic the use of generics and factories used in the Web Service Software Factory to make my own DAO's. Here is an example that shows a best case example of grabbing an Order Entity from an Orders Table based on the primary key, OrderId:
public class OrderDAO : DAO<Order>
{
public OrderDAO(IDatabase database) : base(database) { }
public Order GetOrderByOrderId(int orderId)
{
return base.FindOne<int>(new GetOrderByOrderIdSelectionFactory(), new OrderFactory(), orderId);
}
}
Obviously there is a lot of code hidden in the factories, GetOrderByOrderIdSelectionFactory and OrderFactory, as well as the FindOne Method, but you gotta love the simplicity and elegance here. Things are nicely packaged here based on responsibility, and the naming is fairly intuitive such that a developer looking at this code for the first time can get an idea of what is happening.
Command Factories - e.g. GetOrderByOrderIdSelectionFactory
We can break down the parts and start to see some of the "necessary" components of an O/R Mapper. The GetOrderByOrderIdSelectionFactory is nothing more than a DbCommand factory. To get our entity from the database, we will eventually need to issue a select command. The GetOrderByOrderIdSelectionFactory creates the command:
public class GetOrderByOrderIdSelectionFactory : ISelectionFactory<int>
{
public DbCommand CreateSelectCommand(IDatabase database, int orderId)
{
DbCommand command = database.CreateStoredProcCommand("GetOrderByOrderId");
database.AddInParameter(command, "OrderId", DbType.Int32, orderId);
return command;
}
}
Object Factories and Data Mapping
When the command executes, an IDataReader will be returned. We need a factory to create the entity based on the values in the DataReader. This is the responsibility of OrderFactory:
public class OrderFactory : IEntityFactory<Order>
{
public Order Construct(IDataReader dr)
{
Order order = new Order();
order.OrderId = (int)dr[(int)OrderFieldIndex.OrderId];
order.Number = (string)dr[(int)OrderFieldIndex.Number];
...
return order;
}
}
Again, this is nice and clean with clear separation of responsibilities.
The Refactored Base DAO Class
The base class has much of the "complexity", but again it is pretty easy to follow:
public class DAO<T> where T : Entity
{
IDatabase _database;
public DAO(IDatabase database)
{
if (database == null)
throw new ArgumentNullException("database");
_database = database;
}
protected T FindOne<TIdentity>(ISelectionFactory<TIdentity> selectionFactory, IEntityFactory<T> entityFactory, TIdentity identity)
{
DbCommand selectCommand = selectionFactory.CreateSelectCommand(_database, identity);
T entity = default (T);
using (IDataReader dr = _database.ExecuteReader(selectCommand))
{
if (dr.Read())
entity = entityFactory.Construct(dr);
entity.AcceptChanges();
}
return entity;
}
}
I made a few changes here and there, but I have stayed true to the concepts in the Data Access Guidance Package.
Ease of Maintenance With Generics and Small Specialized Factory Classes
The introduction of generics saves a lot of code, but the introduction of the small specialized factory classes obviously creates more classes ( not necessarily more code ). The beauty of these small specilialized classes supporting the single-responsibility principle is that they allow for easier maintenance over the long run.
It is easy to replace and change factories as needed with little effort and little distraction.
Moving Forward - Searching Orders
For searching orders we might introduce an OrderSearchCriteria Class ( Query Object ) to hold search options. This fits nicely with the infrastructure, albeit one has to decide how to map the class to T-SQL :)
public class SearchForOrdersSelectionFactory : ISelectionFactory<OrderSearchCriteria>
{
public DbCommand CreateSelectCommand(IDatabase database, OrderSearchCriteria criteria)
{
// Generate DbCommand Based on OrderSearchCritiera...
return command;
}
}
We may get multiple orders ( List<Order> ) when searching. The OrderDAO method would look nice and clean as follows:
public List<Order> SearchOrders(OrderSearchCriteria criteria)
{
return base.FindAll<OrderSearchCriteria>(new SearchForOrdersSelectionFactory(), new OrderFactory(), criteria);
}
The FindAll method handles the details, keeping a nice and refactored architecture.
protected List<T> FindAll<TIdentity>(ISelectionFactory<TIdentity> selectionFactory, IEntityFactory<T> entityFactory, TIdentity identity)
{
DbCommand selectCommand = selectionFactory.CreateSelectCommand(_database, identity);
List<T> list = new List<T>();
using (IDataReader dr = _database.ExecuteReader(selectCommand))
{
while (dr.Read())
{
T entity = entityFactory.Construct(dr);
entity.AcceptChanges();
list.Add(entity);
}
}
return list;
}
Code Generation
This all screams code generation, which is what you get from the Data Access Guidance Package. You also get this from Codus, .netTiers using CodeSmith, LLBLGen Pro, etc. One shouldn't be writing the majority of this code by hand except where performance and maintainability / sustainability require it.
Conclusion
The use of generics and factories in the data access layer can reduce the amount of code as well as improve the overall maintability of your code over the long run by keeping cohesion high and responsibilities clear. Although it is important to understand the concepts, I would reach out for any one of the O/R Mappers and Code Generation Tools to help you with much of the grunt work.
by David Hayden
Posted
Tue, Oct 24 2006 8:42 PM
by
David Hayden