Sergey Shishkin

on agile software development

Archive for the ‘Data Access’ Category

Unit Of Work Pattern Revised

The last post in my data access trilogy (see also repository and specification) is about the Unit of Work pattern.

When working with NHibernate you have a unit of work for free as its ISession interface. Moreover, one have to use ISession in some way in order to get something useful out of NHibernate. However, using it directly in the application layer is very dangerous. The application gets coupled to NHibernate forever. It’s nothing even close to the Onion Architecture.

Another approach would be to introduce yet another layer of abstraction. This general approach can solve any problem except the problem of too many layers of abstraction. There should be a simpler way to manage connections, transactions and units of work, and all that not coupled to NHibernate!

Back To The Beginning

Let’s assume for a moment that we couple the application to NHibernate. Then what we might do in an application layer service method is something like this:


using
(var session = SessionFactory.OpenSession())

using (var tx = session.BeginTransaction())

{

    //…

 

    session.Flush();

    tx.Commit();

}

Now in that using scope we have a session instance that we use to instantiate repositories and do all the work. Repositories don’t have to manage neither connections, nor transactions, nor units of work. They just write to the provided session.

Transactions Are Units Of Work

An important fact to notice about the code snippet above is that it runs the unit of work in a transaction. So why then we have to code against two separate abstractions (NHibernate’s ISession and ITransaction) when they both mean the same concept (unit of work) to the application?

A much better way would be to use only one abstraction instead and let it manage the others under the hood. But wait starting to code the IUnitOfWork interface. There is a yet simpler solution.

Plain old .NET ambient transactions (which I assume every .NET developer is already familiar with) can represent units of work for the application layer:


using
(var tx = new TransactionScope())

{

    //…

 

    tx.Complete();

}

The application layer then has no dependencies whatsoever to NHibernate. As a bonus we get a transaction across any resources that support ambient transactions (MSMQ, for example) (although transactions over multiple resources come with the price of Distributed Transactional Coordinator).

It is now responsibility of the repository implementation to obtain an appropriate session (get current or open new) whenever the repository is called. The transaction’s completed event can be used to flush the session if the transaction has been committed and close it to free the connection.

Logging And Auditing

But wait, what about logging? How can I log an error to a transactional store, if it automatically gets rolled back? To suppress the current ambient transaction in special cases (like logging) the TransactionScope supports options:


using
(var noTx = new TransactionScope(TransactionScopeOption.Suppress))

{

    log.Write(exception);

 

    noTx.Complete();

}

Isolation Level

There is also an overload to specify the isolation level of the transaction, when tuning performance of concurrent requests:


using
(var tx = new TransactionScope(

    TransactionScopeOption.Required,

    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))

{

    // read-only operation here

 

    tx.Complete();

}

Summary

In this post I described how one can reuse the common .NET abstraction of ambient transactions as a unit of work and thus hide the infrastructure away from application developers and avoid introducing new abstractions into the application. In this approach coupling is inverted relative to the first code sample. Now the session (NHibernate-specific unit of work) is coupled to the .NET ambient transaction (application unit of work), and not the other way around. Still one can implement advanced connection management under the hood to support batching of multiple transactions (units of work) in a single database connection.

Series Summary

In the last three posts (see also repository and specification) I described how one can implement data access infrastructure while keeping the rest of the application independent of it. I use NHibernate as the O/R Mapper of choice, though the principles described here are applicable to almost any .NET data access technology. Once again, the principles are:

  • Use the repository pattern to abstract the data access infrastructure;
  • Use Linq and the specification pattern to abstract queries;
  • Use .NET ambient transactions as units of work.

When sticking to these principles, the specific data access infrastructure (be it NHibernate or anything) can be hidden away from application developers and hence lower the learning curve in a project. Of course, somebody still has to deal with actual mappings, but it can be the DBA! 😉

 

Written by Sergey Shishkin

17.08.2008 at 20:25

Posted in Data Access

Specification Pattern Revised

(see also repository and unit of work)

Unfortunately, current implementation of Linq2NH does not support queries like this:


from
customer in repository.AllEntities()

where specification.IsSatisfiedBy(customer)

select customer

The cause is that Linq2NH can’t translate the call to the specification’s method into the appropriate call to the Criteria API. What can be done instead is encapsulation of queries into C# extension methods. We can do this in two ways:

Chaining IQueryable<T>

First one is chaining of IQueryable<T> methods like this:


public
static IQueryable<Customer> FindByName(this IQueryable<Customer> customers, string query)

{

    return

        from customer in customers

        where customer.FirstName.StartsWith(query) ||

            customer.LastName.StartsWith(query)

        select customer;

}


from
customer in repository.AllEntities().FindByName(name)

select customer;

Extending IRepository<T>

Alternatively, you can write extension methods for IRepository<Customer> like this:


public
static Collection<Customer> FindByName(this IRepository<Customer> repository, string query)

{

    var linq =

        from customer in repository.AllEntities()

        where customer.FirstName.StartsWith(query) ||

            customer.LastName.StartsWith(query)

        select customer;

    return new Collection<Customer>(linq.ToList());

}


var
customers = customerRepository.FindByName(name);

These two approaches are not a mutual exclusion. You can apply them together for better results. Encapsulate simple queries into IQueryable<T> extension methods. Then combine them to implement more complex IRepository<T> extension methods, available to the application layer.

 

Written by Sergey Shishkin

17.08.2008 at 16:32

Posted in Data Access

Repository Pattern Revised

(see also specification, unit of work)

It Should Not Be That Complex

Last week I was implementing NHibernate-based data access infrastructure. While looking for existing implementations I saw a lot of code, where NHibernate leaks into the domain layer either in the form of queries (HQL or Criteria API) or a Unit of Work. Although some of the implementations succeed in decoupling the infrastructure and the domain layers, still these implementations are much too complex to maintain. For example, if you abstract complex queries with Specifications, you still have specification implementations coupled to NHibernate. Also manipulating pure ISession objects in the application layer is commonly considered to be OK.

This post series provides a much simpler look at the implementation of the good old repository (this post), specification and unit of work patterns.

Linq To Rescue

Though we have some issues with Linq to SQL, the Linq itself allows us to get rid of HQL and Criteria in our domain layer. Thanks to Linq to NHibernate (Linq2NH) we can now abstract NHibernate queries with the nice Linq syntax. Meet the simplest ever repository interface:

public interface IRepository<T>

{

    void Save(T entity);

    void Delete(T entity);

    T Get(object key);

    IQueryable<T> AllEntities();

}

Now we can write client code like this:

var customers =

    from customer in repository.AllEntities()

    where customer.FirstName.StartsWith(name) ||

        customer.LastName.StartsWith(name)

    select customer;

Queries are now expressed in Linq and are not coupled to the infrastructure. Great! But now queries are all around. Let’s encapsulate them.

 

Written by Sergey Shishkin

17.08.2008 at 16:32

Posted in Data Access