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:
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]
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:
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/
8 comments:
Thanks a lot. I was really wanting this from your last post. I'll be back with questions. Keep up the good work.
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
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.
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...
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 ;)
Many thanks for this and the other posts in the series. Looking forward to more!
Great Series!
Could you atart a topic for this on the alt.net forum?
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? :)
Post a Comment