What do you do after watching 12 episodes of The Office seasons 1 & 2?
- Some people may have smoke up
- have a brew or two... okay fine... get gunned!
- Some people may DJ (I usually do that)
As for me, this particular night was to post some sample code, some nyahhh and some tests for the Unit of Work pattern (UoW). I can't believe there is no sample code out there for this pattern. I'm just under the assumption that a lot of people that know about this pattern are not willing to share the love. So I ended up whipping up the pattern with unit tests! This may be some uber nyahhh for some... Ah well.
What is Unit of Work?
From kinda looking at the Fowler P of EAA book, it is more like an item that keeps track of objects (Domain Objects) for any changes whether it is a new object (an insert to the db), changes/modifications to any properties (update), or removing an item (delete). It does all of these changes as one big dump to the db.
UoW seems to be pretty efficient when it comes to database calls. Instead of doing the usual the service layer calls some mapper to do an insert for one item (which is slow, depending on how you have set up your db transactions), UoW keeps track of all the business transaction that affects the db. A programmer does not have to explicitly call any data mappers for any database changes- UoW does it all for ya'll!
Lets see some code!!!
Before we begin, I will warn all of you reading this, that there are no comments what so ever. I tried to make the Tests (busted some TDD stylez) speak for the pattern it self. If you can't read the test... learn TDD or even Unit Tests foo!
The Test
Note: I instanciated "ExtendedUnitOfWork" (inherit from UnitOfWork class) in order to override my DatabaseConnection object to use a Mock.
using NUnit.Framework;using unitofworkspike;namespace UnitOfWorkTest{ [TestFixture]public class UnitOfWorkTest
{private MockMapper mapper;
[TestFixtureSetUp]public void TestFixtureSetUp()
{mapper = new MockMapper();
MapperFactory.Instance.AddMapper(typeof (SimpleObject), mapper);
}
[TearDown]public void TearDown()
{mapper.Reset();
}
[Test]public void ShouldInsertSimpleObject()
{ExtendedUnitOfWork work = new ExtendedUnitOfWork();
CreateNewSimpleObject(work);
work.Commit();
Assert.IsTrue(mapper.HasCalledInsert); Assert.IsFalse(mapper.HasCalledUpdate); Assert.IsFalse(mapper.HasCalledDelete); Assert.AreEqual(1, mapper.InsertCount); Assert.AreEqual(0, mapper.UpdateCount); Assert.AreEqual(0, mapper.DeleteCount);}
[Test]public void ShouldInsertMultipleSimpleObjects()
{ExtendedUnitOfWork work = new ExtendedUnitOfWork();
CreateNewSimpleObjects(work, 5);
work.Commit();
Assert.IsTrue(mapper.HasCalledInsert); Assert.IsFalse(mapper.HasCalledUpdate); Assert.IsFalse(mapper.HasCalledDelete); Assert.AreEqual(5, mapper.InsertCount); Assert.AreEqual(0, mapper.UpdateCount); Assert.AreEqual(0, mapper.DeleteCount);}
[Test]public void ShouldUpdateOneSimpleObjectWithOneInsert()
{ExtendedUnitOfWork work = new ExtendedUnitOfWork();
CreateNewSimpleObject(work);
CreateToEditSimpleObject(work, 1);
work.Commit();
Assert.IsTrue(mapper.HasCalledInsert); Assert.IsTrue(mapper.HasCalledUpdate); Assert.IsFalse(mapper.HasCalledDelete); Assert.AreEqual(1, mapper.InsertCount); Assert.AreEqual(1, mapper.UpdateCount); Assert.AreEqual(0, mapper.DeleteCount);}
[Test]public void ShouldUpdateMultipleSimpleObjects()
{ExtendedUnitOfWork work = new ExtendedUnitOfWork();
CreateEditSimpleObjects(work, 4);
work.Commit();
Assert.IsFalse(mapper.HasCalledInsert); Assert.IsTrue(mapper.HasCalledUpdate); Assert.IsFalse(mapper.HasCalledDelete); Assert.AreEqual(0, mapper.InsertCount); Assert.AreEqual(4, mapper.UpdateCount); Assert.AreEqual(0, mapper.DeleteCount);}
[Test]public void ShouldRemoveSimpleObject()
{ExtendedUnitOfWork work = new ExtendedUnitOfWork();
CreateToDeleteSimpleObject(work, 1);
work.Commit();
Assert.IsFalse(mapper.HasCalledInsert); Assert.IsFalse(mapper.HasCalledUpdate); Assert.IsTrue(mapper.HasCalledDelete); Assert.AreEqual(0, mapper.InsertCount); Assert.AreEqual(0, mapper.UpdateCount); Assert.AreEqual(1, mapper.DeleteCount);}
[Test]public void ShouldRemoveMultipleSimpleObjects()
{ExtendedUnitOfWork work = new ExtendedUnitOfWork();
CreateToDeleteSimpleObjects(work, 7);
work.Commit();
Assert.IsFalse(mapper.HasCalledInsert); Assert.IsFalse(mapper.HasCalledUpdate); Assert.IsTrue(mapper.HasCalledDelete); Assert.AreEqual(0, mapper.InsertCount); Assert.AreEqual(0, mapper.UpdateCount); Assert.AreEqual(7, mapper.DeleteCount);}
[Test]public void ShouldDoMultipleJobsForUnit()
{ExtendedUnitOfWork work = new ExtendedUnitOfWork();
CreateNewSimpleObjects(work, 9);
CreateEditSimpleObjects(work, 8);
CreateToDeleteSimpleObjects(work, 3);
work.Commit();
Assert.IsTrue(mapper.HasCalledInsert); Assert.IsTrue(mapper.HasCalledUpdate); Assert.IsTrue(mapper.HasCalledDelete); Assert.AreEqual(9, mapper.InsertCount); Assert.AreEqual(8, mapper.UpdateCount); Assert.AreEqual(3, mapper.DeleteCount);}
private void CreateNewSimpleObjects(ExtendedUnitOfWork work, int count)
{for (int i = 0; i < count; i++)
{CreateNewSimpleObject(work);
}
}
private void CreateEditSimpleObjects(ExtendedUnitOfWork work, int count)
{for (int i = 0; i < count; i++)
{CreateToEditSimpleObject(work, i);
}
}
private void CreateToDeleteSimpleObjects(ExtendedUnitOfWork work, int count)
{for (int i = 0; i < count; i++)
{CreateToDeleteSimpleObject(work, i);
}
}
private void CreateToEditSimpleObject(ExtendedUnitOfWork work, int id)
{SimpleObject simple = new SimpleObject(work);
simple.Id = id;
simple.IsTrue = true; simple.Text = "ExistingItem";}
private void CreateToDeleteSimpleObject(ExtendedUnitOfWork work, int id)
{SimpleObject simple = SimpleObject.Remove(work);
simple.Id = id;
simple.IsTrue = true; simple.Text = "DeleteItem";}
private static void CreateNewSimpleObject(ExtendedUnitOfWork work)
{SimpleObject simple = SimpleObject.Create(work);
simple.IsTrue = false; simple.Text = "Test";}
private class ExtendedUnitOfWork : UnitOfWork
{protected override IDatabaseConnection GetDatabaseConnection()
{return new MockDatabaseConnection();
}
}
private class MockDatabaseConnection : IDatabaseConnection
{public void Dispose()
{}
}
private class MockMapper : IMapper
{public bool HasCalledInsert;
public int InsertCount = 0;
public bool HasCalledUpdate;
public int UpdateCount = 0;
public bool HasCalledDelete;
public int DeleteCount = 0;
public void Insert(IDatabaseConnection connection, DomainObject domainObject)
{ HasCalledInsert = true;InsertCount++;
}
public void Update(IDatabaseConnection connection, DomainObject domainObject)
{ HasCalledUpdate = true;UpdateCount++;
}
public void Delete(IDatabaseConnection connection, DomainObject domainObject)
{ HasCalledDelete = true;DeleteCount++;
}
public void Reset()
{ HasCalledInsert = false;InsertCount = 0;
HasCalledUpdate = false;UpdateCount = 0;
HasCalledDelete = false;DeleteCount = 0;
}
}
private class SimpleObject : DomainObject
{private int id;
private string text;
private bool isTrue;
public SimpleObject(UnitOfWork unitofwork) : base(unitofwork)
{}
public SimpleObject() : base(null)
{}
public static SimpleObject Create(UnitOfWork unitofwork)
{SimpleObject simple = new SimpleObject(unitofwork);
simple.MarkNew();
return simple;}
public static SimpleObject Remove(UnitOfWork unitofwork)
{SimpleObject simple = new SimpleObject(unitofwork);
simple.MarkRemove();
return simple;}
public int Id
{get { return id; }
set { id = value;MarkDirty();
}
}
public string Text
{get { return text; }
set { text = value;MarkDirty();
}
}
public bool IsTrue
{get { return isTrue; }
set { isTrue = value;MarkDirty();
}
}
}
}
}
The base Domain Object
namespace unitofworkspike{public class DomainObject : IDomainObject
{private UnitOfWork unitofwork;
private bool isMarkedNew;
private bool isMarkedRemove;
private bool isMarkedDirty;
public DomainObject(UnitOfWork unitofwork)
{ this.unitofwork = unitofwork;}
public void MarkNew()
{if(unitofwork == null) return;
unitofwork.RegisterNew(this); isMarkedNew = true;}
public void MarkDirty()
{if (unitofwork == null) return;
if (isMarkedDirty) return;
if (!isMarkedNew && !isMarkedRemove) { unitofwork.RegisterDirty(this); isMarkedDirty = true;}
}
public void MarkRemove()
{if (unitofwork == null) return;
unitofwork.RegisterRemoved(this); isMarkedRemove = true;}
}
}
namespace unitofworkspike{public interface IDomainObject
{ void MarkNew(); void MarkDirty(); void MarkRemove();}
}
Unit of Work
using System.Collections.Generic;using unitofworkspike;namespace unitofworkspike{public class UnitOfWork : IUnitOfWork
{private IList<DomainObject> newObjects = new List<DomainObject>();
private IList<DomainObject> dirtyObjects = new List<DomainObject>();
private IList<DomainObject> removedObjects = new List<DomainObject>();
public void RegisterNew(DomainObject domainObject)
{newObjects.Add(domainObject);
}
public void RegisterDirty(DomainObject domainObject)
{dirtyObjects.Add(domainObject);
}
public void RegisterRemoved(DomainObject domainObject)
{removedObjects.Add(domainObject);
}
public void Commit()
{using (IDatabaseConnection connection = GetDatabaseConnection())
{InsertNew(connection);
UpdateDirty(connection);
DeleteRemoved(connection);
}
}
private void InsertNew(IDatabaseConnection connection)
{foreach (DomainObject newObject in newObjects)
{ MapperFactory.Instance.GetMapper(newObject.GetType()).Insert(connection, newObject);}
}
private void UpdateDirty(IDatabaseConnection connection)
{foreach (DomainObject dirtyObject in dirtyObjects)
{ MapperFactory.Instance.GetMapper(dirtyObject.GetType()).Update(connection, dirtyObject);}
}
private void DeleteRemoved(IDatabaseConnection connection)
{foreach (DomainObject removedObject in removedObjects)
{ MapperFactory.Instance.GetMapper(removedObject.GetType()).Delete(connection, removedObject);}
}
protected virtual IDatabaseConnection GetDatabaseConnection()
{return new DatabaseConnection();
}
}
}
Note: Some of you people that knows UoW may notice I did not implement RegisterClean(DomainObject domainObject) and RollBack(). I just wanted to see the CrUD done... I'm too lazy to do the other ones.
namespace unitofworkspike{public interface IUnitOfWork
{void RegisterNew(DomainObject domainObject);
void RegisterDirty(DomainObject domainObject);
void RegisterRemoved(DomainObject domainObject);
void Commit();}
}

3 comments:
Hi Jonas,
Im having troubles with IDatabaseConnection and IMapper from your sample code.
From what namespace did you get these interfaces?
And also the MapperFactory...
Thanks
Yo Charles,
Check out this post. It has updates on that UoW pattern
http://blog.ezed.ca/2007/03/unit-of-work-pattern-take-2.html
Jonas
Thanks.
I'll check it out.
Post a Comment