Tuesday, August 19, 2008

Persistence Ignorance and DDD with RepositoryBase

[Edit: Fixed the url to the source code.

In my previous post, Implementing Repository and Specification patterns using Linq, I implemented a Repository pattern that allows consumers of the repository to use Linq to query the repository. Along with implementing a Repository pattern I also implemented a pattern for defining Specifications that could be used by not only Domain objects but also can be used with the Repository implementation for querying.

In response to that post, there were a number of comments regarding how the RepositoryBase could be used to implement a Repository for Linq to SQL and also questions surrounding persistence and unit of work pattern. In this post I set out to create a sample project that uses the RepositoryBase for implementing the repository pattern using Domain Driven Design. The sample with also demonstrate how Persistence Ignorance can be achieved using the RepositoryBase. In the sample I will show how you can easily switch the persistence layer between Linq to SQL and NHibernate. (I won’t go into Entity Framework in this post because that’s a whole different can of worms.)

Before I go ahead and start with the sample a note first; the Unit Of Work implementation provided in this sample is a not a solid implementation that should not be considered as guidance or the right way to implement the pattern. If you would like more information on the Unit Of Work pattern please see the reference links at the end of this post. I will not be going into detail on the Unit of Work pattern in this post as that requires a whole new post.

With that out of the way, lets start implementing the sample. The sample application that I will implement in this post will be on the lines of a classic Customer Ordering application. So we have a customer which can make orders which in turn has order lines containing details of the quantuty, price and Product that the customer has ordered.

The Domain Model:

Below is the Domain Model of the Sample application:

Model

The Model is fairly vanilla, where we have a Customer domain entity, that holds a list of Order instances. The Order domain entity holds instances of OrderDetail entities that reference a Product entity. Based on the dommain, the Customer and Order would be the Aggregate Roots.

Building a base for Unit Of Work:

Okay, so the first thing is to layout a foundation for the Unit of Work pattern. The Unit of Work pattern provides a way to keep a track of all changes you make as a single unit and persists all those changes to the data store as a single unit. What the Unit Of Work allows is making changes to the data store as a single unit, avoiding individual updates to the data store which lets to multiple trips to the data store.

As always, I start with an interface;

    5     public interface IUnitOfWork : IDisposable

    6     {

    7         void BeginTransaction();

    8         void CommitTransaction();

    9         void RollbackTransaction();

   10     }

So here I have defined a IUnitOfWork interface that provides has three methods that deal with managing transactions. To provide concrete implementations of IUnitOfWork there is a UnitOfWork class that has two methods on it, Start and Finish.

    5     public static class UnitOfWork

    6     {

    7         #region fields

    8         private static IUnitOfWork _currentUnitOfWork;

    9         #endregion

   10 

   11         #region properties

   12         public static IUnitOfWork Current

   13         {

   14             get { return _currentUnitOfWork; }

   15         }

   16         #endregion

   17 

   18         #region methods

   19         public static IUnitOfWork Start ()

   20         {

   21             if (_currentUnitOfWork != null)

   22                 return _currentUnitOfWork;

   23             return _currentUnitOfWork = IoC.Container.Resolve<IUnitOfWork>();

   24         }

   25 

   26         public static void Finish (bool commit)

   27         {

   28             if (_currentUnitOfWork == null)

   29                 throw new InvalidOperationException();

   30             if (commit)

   31                 _currentUnitOfWork.CommitTransaction();

   32             _currentUnitOfWork.Dispose();

   33             _currentUnitOfWork = null;

   34         }

   35         #endregion

   36     }

So to start a Unit of Work, we can call the Start method on the UnitOfWork which gets a concrete implementation of IUnitOfWork from a IoC container. Once we are done we can call the Finish method.

Note: This is probably the least desirable way to implement the UnitOfWork pattern, but this is done in this sample to provide the simplest way of starting and finishing a unit of work.

Implementing the IUnitOfWork and RepositoryBase for Linq to SQL:

When starting to implement a repository that uses Linq To SQL, I chose to define a external XML mapping file to define the Linq to SQL mapping instead of using the Linq to SQL designer. This allows me to keep my domain objects free from having infrastructure concerns and not being tied to Linq To SQL allowing me to replace Linq to SQL with NHibernate. But using a mapping file to define the Linq to SQL mapping also has it’s downside. First is that we cannot take advantage of the lazy loading functionality that Linq to SQL provides. This is because Linq To SQL expects lazy loaded objects and collections be defined as EntityRef<T> and EntitySet<T> types. I could define the associations in the domain model as EntityRef<T> and EntitySet<T> but that would introduce infrastructure concerns into the domain thus making it difficult for me to replace Linq to SQL with NHibernate later on.

So I have decided to skip on lazy loading for now. Defining a mapping file for Linq to SQL is pretty easy and straight forwarding and you could always use SqlMetal to get a Xml mapping file generated from your database. Below is the complete mapping file:

    1 <?xml version="1.0" encoding="utf-8" ?>

    2 <Database xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007" Name="LinqRepositorySampleDB">

    3   <Table Name="dbo.Customers" Member="Customers">

    4     <Type Name="LinqRepositorySample.Domain.Customer">

    5       <Column Name="CustomerID" Member="CustomerId" DbType="Int NOT NULL IDENTITY"

    6               Storage="_customerId" IsPrimaryKey="true" IsDbGenerated="true"/>

    7       <Column Name="FirstName" Member="FirstName" DbType="VarChar(200) NOT NULL"/>

    8       <Column Name="LastName" Member="LastName" DbType="VarChar(200) NOT NULL"/>

    9       <Column Name="StreetAddress1" Member="StreetAddress1" DbType="VarChar(500) NOT NULL"/>

   10       <Column Name="StreetAddress2" Member="StreetAddress2" DbType="VarChar(500) NULL"/>

   11       <Column Name="City" Member="City" DbType="VarChar(100) NOT NULL"/>

   12       <Column Name="State" Member="State" DbType="Char(2) NOT NULL"/>

   13       <Column Name="ZipCode" Member="ZipCode" DbType="VarChar(5) NOT NULL"/>

   14       <Association Name="Customer_Orders" Member="Orders" Storage="_orders"

   15                   ThisKey="CustomerId" OtherKey="CustomerId"/>

   16     </Type>

   17   </Table>

   18   <Table Name="dbo.Products" Member="Products">

   19     <Type Name="LinqRepositorySample.Domain.Product">

   20       <Column Name="ProductID" Member="ProductId" Storage="_productId" DbType="Int NOT NULL IDENTITY"

   21               IsPrimaryKey="true" IsDbGenerated="true"/>

   22       <Column Name="ProductName" Member="Name" DbType="VarChar(200) NOT NULL"/>

   23       <Column Name="ProductCategory" Member="Category" DbType="VarChar(50) NOT NULL"/>

   24       <Column Name="StandardCost" Member="StandardCost" DbType="Money NOT NULL"/>

   25       <Column Name="StandardPrice" Member="StandardPrice" DbType="Money NOT NULL"/>

   26     </Type>

   27   </Table>

   28   <Table Name="dbo.Orders" Member="Orders">

   29     <Type Name="LinqRepositorySample.Domain.Order">

   30       <Column Name="OrderID" Member="OrderId" Storage="_orderId"

   31               DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true"/>

   32       <Column Name="CustomerID" Member="CustomerId" CanBeNull="true"/>

   33       <Association Name="Order_Customer" Member="Customer" Storage="_customer"

   34                   ThisKey="CustomerId" OtherKey="CustomerId" IsForeignKey="true" />

   35       <Column Name="OrderDate" Member="OrderDate" DbType="DateTime NOT NULL"/>

   36       <Column Name="ScheduledDeliveryDate" Member="ScheduledDelivery" DbType="DateTime NOT NULL"/>

   37       <Column Name="Delivered" Member="Delivered" DbType="Bit NOT NULL"/>

   38       <Association Name="Order_OrderDetails" Member="OrderDetails" Storage="_orderDetails"

   39                   ThisKey="OrderId" OtherKey="OrderId" />

   40     </Type>

   41   </Table>

   42   <Table Name="dbo.OrderDetails" Member="OrderDetails">

   43     <Type Name="LinqRepositorySample.Domain.OrderDetail">

   44       <Column Name="OrderLineID" Member="OrderDetailId" Storage="_orderDetailId"

   45               DbType="Int IDENTITY NOT NULL" IsPrimaryKey="true" IsDbGenerated="true" />

   46       <Column Name="ProductID" Member="ProductId"/>

   47       <Column Name="OrderID" Member="OrderId" CanBeNull="true"/>

   48       <Column Name="Total" Member="Total"/>

   49       <Association Name="OrderDetail_Order" Member="Order" Storage="_order"

   50                   ThisKey="OrderId" OtherKey="OrderId" IsForeignKey="true" />

   51       <Association Name="OrderDetail_Product" Member="Product" ThisKey="ProductId"

   52                   OtherKey="ProductId" IsForeignKey="true" />

   53       <Column Name="Qty" Member="Qty" DbType="Int NOT NULL"/>

   54       <Column Name="Price" Member="Price" DbType="Money NOT NULL"/>

   55     </Type>

   56   </Table>

   57 </Database>

The next step is to provide an implementation of IUnitOfWork that creates a DataContext that consumes the above mapping file. Here it is:

    1 using System;

    2 using System.Configuration;

    3 using System.Data;

    4 using System.Data.Common;

    5 using System.Data.Linq;

    6 using System.Data.Linq.Mapping;

    7 using System.IO;

    8 using LinqRepositorySample.Domain;

    9 

   10 namespace LinqRepositorySample.Data.LinqToSql

   11 {

   12     public class LinqUnitOfWork : IUnitOfWork

   13     {

   14         #region fields

   15         private DataContext _currentContext;

   16         private DbTransaction _currentTransaction;

   17         private bool _disposed;

   18         #endregion

   19 

   20         #region .ctor

   21         public LinqUnitOfWork ()

   22         {

   23             CreateDataContext();

   24         }

   25 

   26         private void CreateDataContext() {

   27             Stream mappings = GetType().Assembly.GetManifestResourceStream("LinqRepositorySample.Data.LinqToSql.mapping.xml");

   28             XmlMappingSource mappingSource = XmlMappingSource.FromStream(mappings);

   29             _currentContext = new DataContext(ConfigurationManager.ConnectionStrings["SampleDB"].ConnectionString,

   30                                               mappingSource);

   31             _currentContext.DeferredLoadingEnabled = false;

   32             DataLoadOptions loadOptions = new DataLoadOptions();

   33             loadOptions.LoadWith<Customer>(x => x.Orders);

   34             loadOptions.LoadWith<Order>(x => x.OrderDetails);

   35             loadOptions.LoadWith<OrderDetail>(x => x.Product);

   36             _currentContext.LoadOptions = loadOptions;

   37         }

   38         #endregion

   39 

   40         #region properties

   41         public DataContext Context

   42         {

   43             get { return _currentContext; }

   44         }

   45         #endregion

   46 

   47         #region Implementation of IDisposable

   48         public void Dispose()

   49         {

   50             if (!_disposed)

   51             {

   52                 Dispose(true);

   53                 GC.SuppressFinalize(this);

   54                 _disposed = true;

   55             }

   56         }

   57 

   58         private void Dispose(bool disposing)

   59         {

   60             if (disposing)

   61             {

   62                 if (_currentTransaction != null)

   63                 {

   64                     _currentTransaction.Dispose();

   65                     _currentTransaction = null;

   66                 }

   67                 if (_currentContext != null)

   68                 {

   69                     _currentContext.Dispose();

   70                     _currentContext = null;

   71                 }

   72             }

   73         }

   74         #endregion

   75 

   76         #region Implementation of IUnitOfWork

   77         public void BeginTransaction()

   78         {

   79             //Ignore: The DataContext uses an implicit transaction and makes all changes made to the data context within one transaction.

   80         }

   81 

   82         public void CommitTransaction()

   83         {

   84             _currentContext.SubmitChanges();

   85         }

   86 

   87         public void RollbackTransaction()

   88         {

   89             CreateDataContext(); //Discarding changes by creating a new data context... probably the most dummest requirement of Linq to SQL

   90         }

   91         #endregion

   92     }

   93 }

So what we have here is a LinqUnitOfWork class that implements the IUnitOfWork interface that creates a new DataContext instance that uses the mapping file that is embedded into the assembly. The other thing to note is that the LinqUnitOfWork is using DataLoadOptions.LoadWith to specify to use eager loading as our domain entities don’t use EntityRef<T> and EntitySet<T> to define associations. The implemenetation of BeginTransaction also does nothing as the DataContext internally wraps all changes made to the data context within a single transaction when calling SubmitChanges(). Also since there is no real way to discard changes made to the DataContext, which in my opinion is just plain WRONG, the only viable way to discard changes is to scrap the old DataContext and create a new one (stupid… stupid… stupid).

The LinqUnitOfWork class also exposes the DataContext contained by the unit of work instances through the Context property.

The next step is to provide an implementation of RepositoryBase that can be used to query using Linq To SQL using the LinqUnitOfWork implementaiton:

    1 using System;

    2 using System.Data.Linq;

    3 using System.Linq;

    4 using LinqRepositorySample.Domain;

    5 

    6 namespace LinqRepositorySample.Data.LinqToSql

    7 {

    8     public class LinqRepository<T> : RepositoryBase<T> where T : class

    9     {

   10 

   11         #region fields

   12         private Table<T> _table;

   13         #endregion

   14 

   15         #region .ctor

   16         public LinqRepository()

   17         {

   18             if (UnitOfWork.Current == null)

   19                 throw new InvalidOperationException();

   20             LinqUnitOfWork uow = UnitOfWork.Current as LinqUnitOfWork;

   21             if (uow == null)

   22                 throw new InvalidOperationException();

   23             _table = uow.Context.GetTable<T>();

   24         }

   25         #endregion

   26 

   27         #region Overrides of RepositoryBase<T>

   28         /// <summary>

   29         /// Gets the <see cref="IQueryable{T}"/> used by the <see cref="RepositoryBase{T}"/>

   30         /// to execute Linq queries.

   31         /// </summary>

   32         /// <value>A <see cref="IQueryable{T}"/> instance.</value>

   33         /// <remarks>

   34         /// Inheritos of this base class should return a valid non-null <see cref="IQueryable{T}"/> instance.

   35         /// </remarks>

   36         protected override IQueryable<T> RepositoryQuery

   37         {

   38             get { return _table.AsQueryable(); }

   39         }

   40 

   41         /// <summary>

   42         /// Marks the entity instance to be saved to the store.

   43         /// </summary>

   44         /// <param name="entity">An instance of <typeparamref name="T"/> that should be saved

   45         /// to the database.</param>

   46         /// <remarks>Implementors of this method must handle both Insert and Update scenarios.</remarks>

   47         public override void Save(T entity)

   48         {

   49             if (entity == null)

   50                 throw new ArgumentNullException();

   51             _table.InsertOnSubmit(entity);

   52         }

   53 

   54         /// <summary>

   55         /// Marks the entity instance to be deleted from the store.

   56         /// </summary>

   57         /// <param name="entity">An instance of <typeparamref name="T"/> that should be deleted.</param>

   58         public override void Delete(T entity)

   59         {

   60             if (entity == null)

   61                 throw new ArgumentNullException();

   62             _table.DeleteOnSubmit(entity);

   63         }

   64         #endregion

   65     }

   66 }

The above code defines a LinqRepository that checks if a UnitOfWork has been started. If so it tries to cast the current unit of work, provided by UnitOfWork.Current, to a LinqUnitOfWork instance. It then uses the Context property to get the DataContext and gets a Table<T> for the generic type and stores it into the _table field which is then used to get an IQueryable by calling _table.ToQueryable() in the RepositoryQuery poperty.

Providing an Unit of Work and Repository implementation for NHibernate:

So to demonstrate persistence ignorance I’m going to implement IUnitOfWork and RepositoryBase for NHibernate. If you have no experience with NHibernate you can see the reference links at the bottom of this post for links on learning about NHibernate or you can skip this section. I will be posting detailed tutorials on NHibernate soon as well.

Just like Linq to SQL, we start with the mapping files for NHibernate. Below is a sample mapping file for the Order entity:

    1 <?xml version="1.0" encoding="utf-8" ?>

    2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"

    3                     assembly="LinqRepositorySample.Domain"

    4                   namespace="LinqRepositorySample.Domain" schema="dbo">

    5   <class name="Order" table="Orders">

    6     <id name="OrderId" column="OrderID" access="nosetter.camelcase-underscore">

    7       <generator class="identity"/>

    8     </id>

    9     <property name="OrderDate" column="OrderDate"/>

   10     <property name="ScheduledDelivery" column="ScheduledDeliveryDate"/>

   11     <property name="Delivered" column="Delivered"/>

   12     <many-to-one name="Customer" access="nosetter.camelcase-underscore"

   13                 column="CustomerID" class="Customer"/>

   14     <bag name="OrderDetails" table="OrderDetails" access="nosetter.camelcase-underscore" inverse="true" cascade="all-delete-orphan">

   15       <key column="OrderID" foreign-key="OrderID"/>

   16       <one-to-many class="OrderDetail"/>

   17     </bag>

   18   </class>

   19 </hibernate-mapping>

The mappings are simple enough since there aren’t any complex associations or relationships in the domain. So once I have the mappings in place for my domain entities, just like for Linq To SQL the next step is to provide an implementation of IUnitOfWork that uses NHibernate. But before I can do that I need a way to create and get an instance of the ISessionFactory that is responsible for creating sessions. In order to do that I have created a static SessionFactory class:

    1 using NHibernate;

    2 using NHibernate.Cfg;

    3 

    4 namespace LinqRepositorySample.Data.NHibernate

    5 {

    6     public class SessionFactory

    7     {

    8         #region fields

    9         public static readonly ISessionFactory Current;

   10         #endregion

   11 

   12         #region ..ctor

   13         static SessionFactory()

   14         {

   15             Current = new Configuration()

   16                             .Configure()

   17                             .AddAssembly(typeof (SessionFactory).Assembly)

   18                             .BuildSessionFactory();

   19         }

   20         #endregion

   21     }

   22 }

The SessionFactory builds a new instance of the ISessionFactory and stores it in a static readonly field called Current. This way my implementation of IUnitOfWork will be able to build ISession instances without having create ISessionFactory instances for each unit of work, which is bad anyways. Again a reminder, the Unit of Work implementation in this sample is not recommended practice or guidance. In fact I would recommend not using the implementation in this sample AT ALL!!

Okay, so below is the implementation of IUnitOfWork for NHibernate:

    1 using System;

    2 using LinqRepositorySample.Domain;

    3 using NHibernate;

    4 

    5 namespace LinqRepositorySample.Data.NHibernate

    6 {

    7     public class NHibernateUnitOfWork : IUnitOfWork

    8     {

    9         #region fields

   10         private ISession _currentSession;

   11         private ITransaction _currentTransaction;

   12         private bool _disposed;

   13         #endregion

   14 

   15         #region .ctor

   16         public NHibernateUnitOfWork()

   17         {

   18             _currentSession = SessionFactory.Current.OpenSession();

   19         }

   20         #endregion

   21 

   22         #region properties

   23         internal ISession Session

   24         {

   25             get { return _currentSession; }

   26         }

   27         #endregion

   28 

   29         #region Implementation of IDisposable

   30         public void Dispose()

   31         {

   32             if (!_disposed)

   33             {

   34                 Dispose(true);

   35                 GC.SuppressFinalize(this);

   36                 _disposed = true;

   37             }

   38         }

   39 

   40         private void Dispose(bool disposing)

   41         {

   42             if (disposing)

   43             {

   44                 if (_currentTransaction != null)

   45                     _currentTransaction.Dispose();

   46                 if (_currentSession != null)

   47                     _currentSession.Dispose();

   48                 _currentTransaction = null;

   49                 _currentSession = null;

   50             }

   51         }

   52         #endregion

   53 

   54         #region Implementation of IUnitOfWork

   55         public void BeginTransaction()

   56         {

   57             _currentTransaction = _currentSession.BeginTransaction();

   58         }

   59 

   60         public void CommitTransaction()

   61         {

   62             if (_currentTransaction != null)

   63                 _currentTransaction.Commit();

   64         }

   65 

   66         public void RollbackTransaction()

   67         {

   68             if (_currentTransaction != null)

   69                 _currentTransaction.Rollback();

   70         }

   71         #endregion

   72     }

   73 }

Nothing fancy there, I simply get a new ISession from the SessionFactory static class and store it in the _currentSession private variable and expose that as a read-only property Session.

Next is inheriting from RepositoryBase for a NHibernate repository that uses the NHibernateUnitOfWork:

    1 using System;

    2 using System.Linq;

    3 using LinqRepositorySample.Domain;

    4 using NHibernate.Linq;

    5 

    6 namespace LinqRepositorySample.Data.NHibernate

    7 {

    8     public class NHibernateRepository<T> : RepositoryBase<T>

    9     {

   10         #region fields

   11         private IQueryable<T> _query;

   12         #endregion

   13 

   14         #region .ctor

   15         public NHibernateRepository()

   16         {

   17             if (UnitOfWork.Current == null)

   18                 throw new ArgumentNullException();

   19             NHibernateUnitOfWork uow = UnitOfWork.Current as NHibernateUnitOfWork;

   20             if(uow == null)

   21                 throw new ArgumentNullException();

   22             _query = uow.Session.Linq<T>();

   23         }

   24         #endregion

   25 

   26         #region Overrides of RepositoryBase<T>

   27         /// <summary>

   28         /// Gets the <see cref="IQueryable{T}"/> used by the <see cref="RepositoryBase{T}"/>

   29         /// to execute Linq queries.

   30         /// </summary>

   31         /// <value>A <see cref="IQueryable{T}"/> instance.</value>

   32         /// <remarks>

   33         /// Inheritos of this base class should return a valid non-null <see cref="IQueryable{T}"/> instance.

   34         /// </remarks>

   35         protected override IQueryable<T> RepositoryQuery

   36         {

   37             get { return _query; }

   38         }

   39 

   40         /// <summary>

   41         /// Marks the entity instance to be saved to the store.

   42         /// </summary>

   43         /// <param name="entity">An instance of <typeparamref name="T"/> that should be saved

   44         /// to the database.</param>

   45         /// <remarks>Implementors of this method must handle both Insert and Update scenarios.</remarks>

   46         public override void Save(T entity)

   47         {

   48             if (entity == null)

   49                 throw new ArgumentNullException();

   50             ((NHibernateUnitOfWork) UnitOfWork.Current).Session.SaveOrUpdate(entity);

   51         }

   52 

   53         /// <summary>

   54         /// Marks the entity instance to be deleted from the store.

   55         /// </summary>

   56         /// <param name="entity">An instance of <typeparamref name="T"/> that should be deleted.</param>

   57         public override void Delete(T entity)

   58         {

   59             if (entity == null)

   60                 throw new ArgumentNullException();

   61             ((NHibernateUnitOfWork)UnitOfWork.Current).Session.Delete(entity);

   62         }

   63         #endregion

   64     }

   65 }

The NHibernateRepository simpley gets the current NHibernateUnitOfWork and then use NHibernate.Linq to get a IQuerable<T> for the specified type from the current session. One note here is that the NHibernate.Linq (inlcuded in the sample under libs) is still under development and has some rough edges. Overall it does it’s job well enough but I wouldn’t recommend using it in production unless it is for simple apps, at least for a while until all kinks have been worked out.

So now I have implementations of IUnitOfWork and RepositoryBase for both Linq To SQL and NHibernate. Lets see if I can make this monkey do tricks…

Using Castle Windsor to switch between Linq to SQL and NHibernate:

So before I sit down writing tests to see if this thing even works, the first thing that needs doing is a way to be able to switch between Linq to SQL implementations and NHibernate implementation. I’m going to use Castle Windsor for that…

Just like I said I am not going into the UnitOfWork pattern in this post, I’m not going to touch IoC and Dependency Injection either. This post is long enough without cramming two more patterns in it. If you’d like to know more about IoC and DI please see the reference links at the end of this post, or look out for future posts on this blog that will go deep into IoC and DI along with Unit of Work.

Ok, now using Windsor is simple enough, I just need to add the necessary configuration entries to specify the components (Note: I know I’m using Windsor here more like a Service Locator than a Dependency Injector, it’s intentional)

    1 <?xml version="1.0" encoding="utf-8" ?>

    2 <configuration>

    3   <configSections>

    4     <section

    5         name="castle"

    6         type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" />

    7   </configSections>

    8 

    9   <connectionStrings>

   10     <add name="SampleDB" connectionString="Server=(local);Database=LinqRepositorySampleDB;Integrated Security=SSPI;"/>

   11   </connectionStrings>

   12   <castle>

   13     <components>

   14       <component id="linqunitofwork"

   15                 service="LinqRepositorySample.Domain.IUnitOfWork, LinqRepositorySample.Domain"

   16                 type="LinqRepositorySample.Data.LinqToSql.LinqUnitOfWork, LinqRepositorySample.Data.LinqToSql"

   17                 lifestyle="transient"/>

   18       <component id="linqrepository"

   19                 service="LinqRepositorySample.Domain.IRepository`1, LinqRepositorySample.Domain"

   20                 type="LinqRepositorySample.Data.LinqToSql.LinqRepository`1, LinqRepositorySample.Data.LinqToSql"

   21                 lifestyle="transient"/>

   22 

   23       <!--<component id="nhunitofwork"

   24               service="LinqRepositorySample.Domain.IUnitOfWork, LinqRepositorySample.Domain"

   25               type="LinqRepositorySample.Data.NHibernate.NHibernateUnitOfWork, LinqRepositorySample.Data.NHibernate"

   26               lifestyle="transient"/>

   27       <component id="nhrepository"

   28                 service="LinqRepositorySample.Domain.IRepository`1, LinqRepositorySample.Domain"

   29                 type="LinqRepositorySample.Data.NHibernate.NHibernateRepository`1, LinqRepositorySample.Data.NHibernate"

   30                 lifestyle="transient"/>-->

   31     </components>

   32   </castle>

   33 </configuration>

   34 

   35 

Notice the commented out entries. Those entries are for NHibernate implementations of IUnitOfWork and IRepository. So when you want to run the tests for NHibernate, simply comment the component registrations for Linq to SQL and un-comment the ones for Nhibernate. The components are registered as with transient lifestyle because I want to get new instances of IUnitOfWork and IRepository everytime I request one. Also note the component registration for the IRepository, the returned type is a generic type based on the requested generic type. What that means is that if I request a IRepository<Product> from Windsor, it will return a LinqRepository<Product> or NHibernateRepository<Product>, based on the registered component. That frees me from implementing a Repository for each possible type.

Does this thing work? Time for tests!

Finally, on to some tests. The test I am going to include in the sample are very basic and definitely do not test every aspect of the domain. You are free to add your own tests to the sample to see if the implementation works as per your requirement. So the first set of tests I want to do is querying… can I query the repository using Linq? Below are the tests:

   11         [Test]

   12         public void Select_All_Products()

   13         {

   14             UnitOfWork.Start();

   15             IRepository<Product> productsRepository = IoC.Container.Resolve<IRepository<Product>>();

   16             var results = from product in productsRepository

   17                           select product;

   18 

   19             Assert.That(results.Count() > 0);

   20             Assert.That(results.Count() == 9);

   21             UnitOfWork.Finish(true);

   22         }

   23 

   24         [Test]

   25         public void Select_Products_With_Category_Liquor()

   26         {

   27             UnitOfWork.Start();

   28             IRepository<Product> productsRepository = IoC.Container.Resolve<IRepository<Product>>();

   29             var results = from product in productsRepository

   30                           where product.Category == "Liquor"

   31                           select product;

   32             Assert.That(results.Count() > 0);

   33             Assert.That(results.Count() == 4);

   34             UnitOfWork.Finish(true);

   35 

   36         }

So the two tests above just run a simple query, one querying all products and the other querying products with category Liquor. Running them with the Linq to SQL implementation has the following results:

[I have included a LinqRepositorySampleDB database with the sample so you can attach that database to a SQL Express instance to run the tests]

image

That’s encouraging… switching to NHibernate works as well!! A step closer to Persistence Ignorance!

Okay lets spice things up… I have added a OrdersSpecification class that is responsible for dishing out instances of Specification<T> that can be considered as re-usable business logic:

    1 using System;

    2 using System.Collections.Generic;

    3 using System.Linq;

    4 using System.Text;

    5 

    6 namespace LinqRepositorySample.Domain

    7 {

    8     public static class OrderSpecifications

    9     {

   10         public static Specification<Order> OrdersWithHighValue ()

   11         {

   12             return new Specification<Order>(order => order.OrderDetails.Sum(

   13                                                                                 detail => detail.Total

   14                                                                             ) > 200);

   15         }

   16     }

   17 }

Lets see if I can use the OdersWithHighValue specification to query for orders:

   56         [Test]

   57         public void Select_Orders_Not_Delivered_And_OfHighValue()

   58         {

   59             UnitOfWork.Start();

   60             IRepository<Order> ordersRepository = IoC.Container.Resolve<IRepository<Order>>();

   61             var ordersWithHighValue = ordersRepository.Query(OrderSpecifications.OrdersWithHighValue());

   62             var results = from order in ordersWithHighValue

   63                           where !order.Delivered

   64                           select order;

   65 

   66             Assert.That(results.Count() > 0);

   67             UnitOfWork.Finish(true);

   68         }

Lets add some more tests, maybe one that tests adding a new Customer with an Order containing order details for all Liquor items in our product catalog:

   53         public void Select_Order_For_Customer_Returns_Only_Orders_For_Specified_Customer()

   54         {

   55             UnitOfWork.Start();

   56             IRepository<Customer> customersRepository = IoC.Container.Resolve<IRepository<Customer>>();

   57             var customer = (from cust in customersRepository

   58                             where cust.CustomerId == 1

   59                             select cust).First();

   60 

   61             IRepository<Order> ordersRepository = IoC.Container.Resolve<IRepository<Order>>();

   62             var results = from order in ordersRepository

   63                           where order.Customer == customer

   64                           select order;

   65 

   66             Assert.That(results.Count() > 0);

   67             UnitOfWork.Finish(true);

   68         }

   69 

   70        [Test]

   71         public void Adding_New_Customer_With_Order_Saves_Customer_And_Order_To_Database ()

   72         {

   73 

   74             IUnitOfWork uow = UnitOfWork.Start();

   75 

   76             IRepository<Product> productsRepository = IoC.Container.Resolve<IRepository<Product>>();

   77             var liquorProducts = from product in productsRepository

   78                                 where product.Category == "Liquor"

   79                                 select product;

   80 

   81 

   82             Customer customer = new Customer();

   83             customer.FirstName = "Test";

   84             customer.LastName = "Test";

   85             customer.StreetAddress1 = "200 Ventura Blvd";

   86             customer.City = "New York City";

   87             customer.State = "NY";

   88             customer.ZipCode = "99999";

   89 

   90             Order order = new Order();

   91             order.Customer = customer;

   92             order.OrderDate = DateTime.Now;

   93             order.ScheduledDelivery = DateTime.Now.AddDays(5);

   94 

   95             Random rnd = new Random();

   96             foreach (var product in liquorProducts)

   97             {

   98                 OrderDetail detail = new OrderDetail();

   99                 detail.Product = product;

  100                 detail.Qty = rnd.Next(1, liquorProducts.Count());

  101                 order.AddOrderDetail(detail);

  102             }

  103 

  104 

  105             uow.BeginTransaction();

  106             IRepository<Customer> customersRepository = IoC.Container.Resolve<IRepository<Customer>>();

  107             customersRepository.Save(customer);

  108             UnitOfWork.Finish(true);

  109 

  110             //Asserts that ID has been assigned properly.

  111             Assert.That(customer.CustomerId != 0);

  112             Assert.That(order.OrderId != 0);

  113             foreach (var detail in order.OrderDetails)

  114             {

  115                 Assert.That(detail.OrderDetailId != 0);

  116             }

  117         }

Okay, running all tests against both NHibernate and Linq to SQL implementation returns:

image

Perfect!!! There you go true persistence ignorance using RepositoryBase. So now you can use either Linq to SQL or NHibernate as your persistence layer and the rest of your application is completely ignorant of the infrastructure choice.

I have added this sample to Rhinestone’s source code, well I don’t have any other place to upload it to. You can get the solutuion from here (SVN): http://rhinestone.googlecode.com/svn/samples/LinqRepositorySample

Some final comments:

Although the above post shows how Persistence Ignorance can be achieved, this is not entirely true with Entity Framework. EF v1.0 does not have good support for POCO and that’s why I intentionally used Linq to SQL in this sample. Additionally, Linq to SQL itself has it’s own issues. Relying on EntitySet<T> and EntityRef<T> for lazy loading pollutes the domain with infrastructure concerns that I don’t want and Linq to SQL cannot support complex inheritance and value objects. I do believe NHibernate’s mapping is at par with Linq to SQL, sometimes more complex considering the options and flexibility provided by NHibernate, but not having a solid dedicated designer for NHibernate definitely hurts. I know there are designers for NHibernate out there, even code gen tools, I still think that NHibernate would be more widely adopted if there were good designers around to generate the mappings.

Alright, this post has gone long enough.

Reference Links:

http://martinfowler.com/eaaCatalog/unitOfWork.html
http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/04/10/nhibernate-and-the-unit-of-work-pattern.aspx
http://martinfowler.com/articles/injection.html
http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx
http://www.summerofnhibernate.com/

Submit this story to DotNetKicks

8 comments:

Outside The Box said...

Thanks a lot. I was really wanting this from your last post. I'll be back with questions. Keep up the good work.

Outside The Box said...

I am unable to get your code. I am prompted for a login at https://rhinestone.googlecode.com/svn/samples/LinqRepositorySample and https://rhinestone.googlecode.com/svn/samples and https://rhinestone.googlecode.com/svn

Can you tell me how to get to this code?

Thanks. Phillip Jacobs

Erik said...

I'm handling the mapping issue similarly to how Rob Conery is handling it - letting the LINQ to SQL designer generate its own objects, then hiding those objects behind a SQL repository - the domain objects are POCOs that I manually map to and from the LINQ to SQL objects.

Ritesh Rao said...

Phillip:
I updated the url to the source code. The original url used https which requires contributor access. You can get the source code from here:
http://rhinestone.googlecode.com/svn/samples/LinqRepositorySample

Sorry about that...

Ritesh Rao said...

erik:

Although that's a perfectly viable option, and I would use that approach if I were ever forced to use Entity Framework v1.0, the fact that the persistence layer now has a lot of left hand right hand code makes it a pain.

You also use the capability of performing partial updates where only updated properties are updated to the database... because you are popupating your POCO manually there's no way for the DataContext to track changes and only make updates to changed properties.

Rob is doing a great job with the MVC Storefront and has some interesting approaches, having to use Linq to SQL as plain data access layer rather than a data mapper is in my opinion a step backwards. Linq to SQL is very capable of POCO support... as long as you are willing to make some trade-offs ;)

Anonymous said...

Many thanks for this and the other posts in the series. Looking forward to more!

Anonymous said...

Great Series!

Could you atart a topic for this on the alt.net forum?

VIAL said...

Hi, Ritesh!
Your posts are great! Thanks a lot!
I've got a question about Domain Model diagram (first screen shot in this post). Is it standard VS class diagram? I see double arrow heads on Customer.Orders association. What is it? Does VS class diagram allow to create collection associations.... Or is it another prety tool, I wonder? :)