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:
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: