David Hayden [MVP C#]

Sponsors

The Lounge

Wicked Cool Jobs

News

  • CodeBetter.Com Home

Other Links

Teas

Patterns & Practices

Florida .NET Developer

Book Reviews

Tampa ASP.NET MVC Developer Group

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Generics and Factories in the Data Access Layer - Code Generation

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

[Advertisement]

Comments

Sergio Pereira wrote re: Generics and Factories in the Data Access Layer - Code Generation
on Wed, Oct 25 2006 7:57 AM

Nice post. Clean and testable foundation for your DAL. Keep it up.

Michael Urquiola wrote re: Generics and Factories in the Data Access Layer - Code Generation
on Wed, Oct 25 2006 9:51 AM

Could someone please explain why this O/R mapping stuff is better than just using a DataTable/DataReader/DataSet?  I keep seeing code like this, but can think of a compelling reason for it.

Mike Griffin wrote re: Generics and Factories in the Data Access Layer - Code Generation
on Wed, Oct 25 2006 11:02 AM

Michael, OR mapping is an excellent time saver. I am one of the authors of EntitySpaces, an OR architecture for the .NET framework. Our architecture is generated by MyGeneration. With our system you can create your entire data objects and business objects in just a few minutes, they will compile clean, be strongly typed, perform very fast, can be regenerated when your object model changes without losing custom code, and can be told to use stored procs or dynamic sql (no recompile necessary) and can be told to run against Oracle, SQL, MySQL with just a config file change (again no recompile). We never use string names like "EmployeeID" so if you drop a column you get a compile error not a runtime error. You can write very cool dynamic queries and be spoon fed all the way via intellisense and so much more. So, if you can get all this in a few minutes why wouldn't you use it?  To top it off you can generate a complete hierarchical object model as well.

A query:

=========

AggregateTestCollection aggTestColl = new AggregateTestCollection();

aggTestColl.Query.es.CountAll = true;

aggTestColl.Query

.Select

(

aggTestColl.Query.IsActive,

aggTestColl.Query.DepartmentID

)

.Where

(

aggTestColl.Query.IsActive.Equal(true)

)

.GroupBy

(

aggTestColl.Query.IsActive,

aggTestColl.Query.DepartmentID

);

aggTestColl.Query.Load();

Saving a record:

=============

Employees emp = new Employees();

emp.AddNew();

emp.FirstName = "David";

emp.LastName = "Hayden";

emp.Save();

Why spend time writing a brand new architecture, use a tested one, generate it, and you will be able spend all those would be hours delivering a killer application that your competition cannot match. That's why folks use them, the ROI is tremendous.

Eugene Ye wrote re: Generics and Factories in the Data Access Layer - Code Generation
on Sun, Oct 29 2006 2:00 AM

Hi, nice article. I am currently looking at how to use the Generic feature for my DAO.

If I am not mistaken, I see that the post uses IoC, similar to the previous posts, where the IDatabase is passed as a constructor parameter. As I am new to this IoC, I need some help here.

1. I don't know how is it better for the IDatabase to be passed into the constructor, rather than having the DAO calls another class that would instantiate the actual IDatabase, before returning to the DAO; if the aim is just to allow for pluggable IDatabase?

2. If, in the future I need to extend the DAO to read/write from multiple IDatabase, how shall I do it? Should I change the constructor to include multiple IDatabase parameter or IDatabase collection? If the DAO retrieve the database connection by itself, wouldn't it be easier to change the DAO to connect to multiple IDatabase, as variable/reference is private within it's class, rather than part of the constructor's contracts?

I am kinda new to IoC, sorry in advance if my questions is shallow or laughable :P but I truly would like to know more. Thanks a great lot.

Eugene

In the meantime, I need to read out the DAO Generic thoroughly before further comments :)

David Hayden [MVP C#] wrote More On Generics and Factories in the Data Access Layer: IoC and Multiple Database Support
on Sun, Oct 29 2006 10:02 AM

In a recent post, Generics and Factories in the Data Access Layer - Code Generation , Eugene had a couple

David Hayden [MVP C#] wrote More On Generics and Factories in the Data Access Layer: IoC and Multiple Database Support
on Sun, Oct 29 2006 10:05 AM

In a recent post, Generics and Factories in the Data Access Layer - Code Generation , Eugene had a couple

Eugene Ye wrote re: Generics and Factories in the Data Access Layer - Code Generation
on Tue, Oct 31 2006 11:02 PM

Hi David, what did you say? Your post seems truncated to me, only the first line can be seen. Thanks.

David Hayden [MVP C#] wrote Validation Application Block - Integrating It Into Your Business Layer
on Thu, Dec 28 2006 5:45 PM

Validation Application Block - Integrating It Into Your Business Layer by David Hayden , Filed: Enterprise

Devlicio.us