I attempted the Unit of Work pattern a couple of times (here) and (here)
But I think during the JP bootcamp, we were talking about how the implementation in the Fowler book is very cluttered. Domain objects know about the unit of work, bla, bla bla... I decided to take a stab on implementing a UoW with Domain objects not knowing about the pattern... Before I go, I kind of cheated with making my domain object know how to mark it self.
And if you want the source code, download it (here).
The Test
1 using System.Collections.Generic;
2 using MbUnit.Framework;
3 using Rhino.Mocks;
4 using unitofwork.spike.Dataaccess;
5 using unitofwork.spike.Domain;
6 using unitofwork.spike.utility;
7
8 namespace unitofwork.spike.test.Task
9 {
10 [TestFixture]
11 public class UnitOfWorkTest
12 {
13 private MockRepository mockery;
14 private IDatabaseConnection connection;
15 private IMapper mapper;
16
17 [SetUp]
18 public void Setup()
19 {
20 mockery = new MockRepository();
21 mapper = mockery.CreateMock<IMapper>();
22 connection = mockery.CreateMock<IDatabaseConnection>();
23 DependencyResolver.Add(typeof (FakeUser), mapper);
24 }
25
26 [TearDown]
27 public void TearDown()
28 {
29 mockery.VerifyAll();
30 DependencyResolver.Remove(typeof (FakeUser));
31 }
32
33 [Test]
34 public void ShouldInstanciate()
35 {
36 mockery.ReplayAll();
37 IUnitOfWork work = new UnitOfWork(connection);
38
39 Assert.IsNotNull(work);
40 }
41
42 [Test]
43 public void Commit_ShouldCommit_OnlyRegisterNew()
44 {
45 FakeUser user = new FakeUser();
46
47 mapper.Save(connection, user);
48 connection.Dispose();
49
50 mockery.ReplayAll();
51
52 IUnitOfWork work = new UnitOfWork(connection);
53
54 work.RegisterNew(user);
55 work.Commit();
56 }
57
58 [Test]
59 public void Commit_ShouldCommitABunchOfStuff()
60 {
61 IList<IDomainObject> fakeUsers = new List<IDomainObject>();
62 FakeUser firstUser = new FakeUser();
63 FakeUser secondUser = new FakeUser();
64 FakeUser thirdUser = new FakeUser();
65 FakeUser fourthUser = new FakeUser();
66 FakeUser fifthUser = new FakeUser();
67
68 firstUser.Mark = Mark.New;
69 secondUser.Mark = Mark.Dirty;
70 thirdUser.Mark = Mark.Delete;
71
72 fakeUsers.Add(firstUser);
73 fakeUsers.Add(secondUser);
74 fakeUsers.Add(thirdUser);
75 fakeUsers.Add(fourthUser);
76 fakeUsers.Add(fifthUser);
77
78 mapper.Save(connection, firstUser);
79 mapper.Update(connection, secondUser);
80 mapper.Delete(connection, thirdUser);
81
82 connection.Dispose();
83
84 mockery.ReplayAll();
85
86 IUnitOfWork work = new UnitOfWork(connection);
87
88 work.Register(fakeUsers);
89 work.Commit();
90 }
91
92 private class FakeUser : IDomainObject
93 {
94 private Mark mark;
95
96 public Mark Mark
97 {
98 get { return mark; }
99 set { mark = value; }
100 }
101 }
102 }
103 }
The Code
1 using System.Collections.Generic;
2 using unitofwork.spike.Dataaccess;
3 using unitofwork.spike.Domain;
4
5 namespace unitofwork.spike.utility
6 {
7 public class UnitOfWork : IUnitOfWork
8 {
9 private readonly IDatabaseConnection connection;
10 private IList<IDomainObject> registerNew;
11 private IList<IDomainObject> registerDirty;
12 private IList<IDomainObject> registerDelete;
13
14 public UnitOfWork(IDatabaseConnection connection)
15 {
16 this.connection = connection;
17 registerNew = new List<IDomainObject>();
18 registerDirty = new List<IDomainObject>();
19 registerDelete = new List<IDomainObject>();
20 }
21
22 public UnitOfWork() : this (DependencyResolver.GetImplementationOf<IDatabaseConnection>(typeof(IDatabaseConnection)))
23 {
24 }
25
26 public void RegisterNew(IDomainObject domainObject)
27 {
28 registerNew.Add(domainObject);
29 }
30
31 public void RegisterDirty(IDomainObject domainObject)
32 {
33 registerDirty.Add(domainObject);
34 }
35
36 public void RegisterDelete(IDomainObject domainObject)
37 {
38 registerDelete.Add(domainObject);
39 }
40
41 public void Register(IList<IDomainObject> domainObjects)
42 {
43 foreach (IDomainObject domainObject in domainObjects)
44 {
45 if (domainObject.Mark != null)
46 {
47 if (domainObject.Mark == Mark.New)
48 RegisterNew(domainObject);
49
50 if (domainObject.Mark == Mark.Dirty)
51 RegisterDirty(domainObject);
52
53 if (domainObject.Mark == Mark.Delete)
54 RegisterDelete(domainObject);
55 }
56 }
57 }
58
59 public void Commit()
60 {
61 using (connection)
62 {
63 try
64 {
65 Save(connection);
66 Update(connection);
67 Delete(connection);
68 }
69 catch
70 {
71 connection.Rollback();
72 }
73 }
74 }
75
76 private void Delete(IDatabaseConnection connection)
77 {
78 foreach (IDomainObject domainObject in registerDelete)
79 {
80 DependencyResolver.GetImplementationOf<IMapper>(domainObject.GetType()).Delete(connection, domainObject);
81 }
82 }
83
84 private void Update(IDatabaseConnection connection)
85 {
86 foreach (IDomainObject domainObject in registerDirty)
87 {
88 DependencyResolver.GetImplementationOf<IMapper>(domainObject.GetType()).Update(connection, domainObject);
89 }
90 }
91
92 private void Save(IDatabaseConnection connection)
93 {
94 foreach (IDomainObject domainObject in registerNew)
95 {
96 DependencyResolver.GetImplementationOf<IMapper>(domainObject.GetType()).Save(connection, domainObject);
97 }
98 }
99 }
100 }
The other Crap
1 namespace unitofwork.spike.Domain
2 {
3 public interface IDomainObject
4 {
5 Mark Mark { get; set; }
6 }
7 }
1 namespace unitofwork.spike.Domain
2 {
3 public sealed class Mark
4 {
5 public static readonly Mark New = new Mark();
6 public static readonly Mark Dirty = new Mark();
7 public static readonly Mark Delete = new Mark();
8 }
9 }
1 using unitofwork.spike.Domain;
2
3 namespace unitofwork.spike.Dataaccess
4 {
5 public interface IMapper
6 {
7 void Save(IDomainObject domainObject);
8 void Save(IDatabaseConnection connection, IDomainObject domainObject);
9 void Update(IDomainObject domainObject);
10 void Update(IDatabaseConnection connection, IDomainObject domainObject);
11 void Delete(IDomainObject domainObject);
12 void Delete(IDatabaseConnection connection, IDomainObject domainObject);
13 }
14 }

