Commit 126b3fc

unknown <mo@.(none)>
2009-09-05 00:18:57
moved the transactions stuff from gorilla commons back in to mo money
1 parent 318b87d
Changed files (83)
build
product
MoMoney.DataAccess
MoMoney.Domain
MoMoney.Presentation
MoMoney.Service
MyMoney
build/lib/app/gorilla/gorilla.commons.infrastructure.dll
Binary file
build/lib/app/gorilla/gorilla.commons.infrastructure.thirdparty.dll
Binary file
build/lib/app/gorilla/gorilla.commons.utility.dll
Binary file
build/lib/app/gorilla/gorilla.commons.windows.forms.dll
Binary file
build/lib/app/gorilla/gorilla.commons.windows.forms.thirdparty.dll
Binary file
build/tools/nant/NAnt.exe.config
@@ -481,7 +481,7 @@
                             hive="LocalMachine" />
                         <readregistry
                             property="sdkInstallRoot"
-                            key="SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.1\WinSDKNetFxTools\InstallationFolder"
+                            key="SOFTWARE\Microsoft\Microsoft SDKs\Windows\v6.0a\WinSDKNetFxTools\InstallationFolder"
                             hive="LocalMachine"
                             failonerror="false" />
                     </project>
build/project.build
@@ -1,5 +1,6 @@
 <project name="momoney">	
 	<property name="project.name" value="${project::get-name()}" />
+  <property name="nant.settings.currentframework" value="net-3.5" />
 
 	<property name="base.dir" value="${directory::get-parent-directory(project::get-base-directory())}" />
 	<property name="product.dir" value="${base.dir}\product" />
product/MoMoney.DataAccess/Transactions/ChangeTracker.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Gorilla.Commons.Infrastructure.Transactions;
+using Gorilla.Commons.Utility.Core;
+using Gorilla.Commons.Utility.Extensions;
+
+namespace MoMoney.DataAccess.Transactions
+{
+    public class ChangeTracker<T> : IChangeTracker<T> where T : IIdentifiable<Guid>
+    {
+        readonly ITrackerEntryMapper<T> mapper;
+        readonly IStatementRegistry registry;
+        readonly IList<ITrackerEntry<T>> items;
+        readonly IList<T> to_be_deleted;
+
+        public ChangeTracker(ITrackerEntryMapper<T> mapper, IStatementRegistry registry)
+        {
+            this.mapper = mapper;
+            this.registry = registry;
+            items = new List<ITrackerEntry<T>>();
+            to_be_deleted = new List<T>();
+        }
+
+        public void register(T entity)
+        {
+            items.Add(mapper.map_from(entity));
+        }
+
+        public void delete(T entity)
+        {
+            to_be_deleted.Add(entity);
+        }
+
+        public void commit_to(IDatabase database)
+        {
+            items.each(x => commit(x, database));
+            to_be_deleted.each(x => database.apply(registry.prepare_command_for(x)));
+        }
+
+        public bool is_dirty()
+        {
+            return items.Count(x => x.has_changes()) > 0 || to_be_deleted.Count > 0;
+        }
+
+        public void Dispose()
+        {
+            items.Clear();
+        }
+
+        void commit(ITrackerEntry<T> entry, IDatabase database)
+        {
+            if (entry.has_changes()) database.apply(registry.prepare_command_for(entry.current));
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/ChangeTrackerFactory.cs
@@ -0,0 +1,24 @@
+using System;
+using Gorilla.Commons.Infrastructure.Container;
+using Gorilla.Commons.Infrastructure.Transactions;
+using Gorilla.Commons.Utility.Core;
+
+namespace MoMoney.DataAccess.Transactions
+{
+    public class ChangeTrackerFactory : IChangeTrackerFactory
+    {
+        readonly IStatementRegistry statement_registry;
+        readonly IDependencyRegistry registry;
+
+        public ChangeTrackerFactory(IStatementRegistry statement_registry, IDependencyRegistry registry)
+        {
+            this.statement_registry = statement_registry;
+            this.registry = registry;
+        }
+
+        public IChangeTracker<T> create_for<T>() where T : IIdentifiable<Guid>
+        {
+            return new ChangeTracker<T>(registry.get_a<ITrackerEntryMapper<T>>(), statement_registry);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/ChangeTrackerFactorySpecs.cs
@@ -0,0 +1,22 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Infrastructure.Transactions;
+using Gorilla.Commons.Testing;
+using Gorilla.Commons.Utility.Core;
+
+namespace MoMoney.DataAccess.Transactions
+{
+    public class ChangeTrackerFactorySpecs
+    {
+    }
+
+    [Concern(typeof (ChangeTrackerFactory))]
+    public class when_creating_a_change_tracker_for_an_item : concerns_for<IChangeTrackerFactory, ChangeTrackerFactory>
+    {
+        it should_return_a_new_tracker = () => result.should_not_be_null();
+
+        because b = () => { result = sut.create_for<IIdentifiable<Guid>>(); };
+
+        static IChangeTracker<IIdentifiable<Guid>> result;
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/ChangeTrackerSpecs.cs
@@ -0,0 +1,104 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Infrastructure.Transactions;
+using Gorilla.Commons.Testing;
+using Gorilla.Commons.Utility.Core;
+
+namespace MoMoney.DataAccess.Transactions
+{
+    public class ChangeTrackerSpecs
+    {
+    }
+
+    [Concern(typeof (ChangeTracker<IIdentifiable<Guid>>))]
+    public abstract class behaves_like_change_tracker :
+        concerns_for<IChangeTracker<IIdentifiable<Guid>>, ChangeTracker<IIdentifiable<Guid>>>
+    {
+        context c = () =>
+                        {
+                            mapper = the_dependency<ITrackerEntryMapper<IIdentifiable<Guid>>>();
+                            registry = the_dependency<IStatementRegistry>();
+                        };
+
+        static protected ITrackerEntryMapper<IIdentifiable<Guid>> mapper;
+        static protected IStatementRegistry registry;
+    }
+
+    [Concern(typeof (ChangeTracker<IIdentifiable<Guid>>))]
+    public class when_commit_that_changes_made_to_an_item : behaves_like_change_tracker
+    {
+        it should_save_the_changes_to_the_database = () => database.was_told_to(x => x.apply(statement));
+
+        context c = () =>
+                        {
+                            item = an<IIdentifiable<Guid>>();
+                            statement = an<IStatement>();
+                            database = an<IDatabase>();
+                            var entry = an<ITrackerEntry<IIdentifiable<Guid>>>();
+
+                            when_the(mapper).is_told_to(x => x.map_from(item)).it_will_return(entry);
+                            when_the(entry).is_told_to(x => x.has_changes()).it_will_return(true);
+                            when_the(entry).is_told_to(x => x.current).it_will_return(item);
+                            when_the(registry).is_told_to(x => x.prepare_command_for(item)).it_will_return(statement);
+                        };
+
+        because b = () =>
+                        {
+                            sut.register(item);
+                            sut.commit_to(database);
+                        };
+
+        static IIdentifiable<Guid> item;
+        static IDatabase database;
+        static IStatement statement;
+    }
+
+    [Concern(typeof (ChangeTracker<IIdentifiable<Guid>>))]
+    public class when_checking_if_there_are_changes_and_there_are : behaves_like_change_tracker
+    {
+        it should_tell_the_truth = () => result.should_be_true();
+
+        context c = () =>
+                        {
+                            item = an<IIdentifiable<Guid>>();
+                            var registration = an<ITrackerEntry<IIdentifiable<Guid>>>();
+
+                            when_the(mapper).is_told_to(x => x.map_from(item)).it_will_return(registration);
+                            when_the(registration).is_told_to(x => x.has_changes()).it_will_return(true);
+                            when_the(registration).is_told_to(x => x.current).it_will_return(item);
+                        };
+
+        because b = () =>
+                        {
+                            sut.register(item);
+                            result = sut.is_dirty();
+                        };
+
+        static bool result;
+        static IIdentifiable<Guid> item;
+    }
+
+    [Concern(typeof (ChangeTracker<IIdentifiable<Guid>>))]
+    public class when_checking_if_there_are_changes_and_there_are_not : behaves_like_change_tracker
+    {
+        it should_tell_the_truth = () => result.should_be_false();
+
+        context c = () =>
+                        {
+                            item = an<IIdentifiable<Guid>>();
+                            var entry = an<ITrackerEntry<IIdentifiable<Guid>>>();
+
+                            when_the(mapper).is_told_to(x => x.map_from(item)).it_will_return(entry);
+                            when_the(entry).is_told_to(x => x.has_changes()).it_will_return(false);
+                        };
+
+        because b = () =>
+                        {
+                            sut.register(item);
+                            result = sut.is_dirty();
+                        };
+
+        static bool result;
+        static IIdentifiable<Guid> item;
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/Context.cs
@@ -0,0 +1,34 @@
+using System.Collections;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class Context : IContext
+    {
+        readonly IDictionary items;
+
+        public Context(IDictionary items)
+        {
+            this.items = items;
+        }
+
+        public bool contains<T>(IKey<T> key)
+        {
+            return key.is_found_in(items);
+        }
+
+        public void add<T>(IKey<T> key, T value)
+        {
+            key.add_value_to(items, value);
+        }
+
+        public T value_for<T>(IKey<T> key)
+        {
+            return key.parse_from(items);
+        }
+
+        public void remove<T>(IKey<T> key)
+        {
+            key.remove_from(items);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/ContextFactory.cs
@@ -0,0 +1,15 @@
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IContextFactory
+    {
+        IContext create_for(IScopedStorage storage);
+    }
+
+    public class ContextFactory : IContextFactory
+    {
+        public IContext create_for(IScopedStorage storage)
+        {
+            return new Context(storage.provide_storage());
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/ContextFactorySpecs.cs
@@ -0,0 +1,31 @@
+using System.Collections;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class ContextFactorySpecs
+    {
+    }
+
+    [Concern(typeof (ContextFactory))]
+    public class when_creating_a_new_context : concerns_for<IContextFactory, ContextFactory>
+    {
+        context c = () =>
+                        {
+                            scope = an<IScopedStorage>();
+                            storage = an<IDictionary>();
+
+                            when_the(scope).is_told_to(x => x.provide_storage()).it_will_return(storage);
+                        };
+
+        because b = () => { result = sut.create_for(scope); };
+
+        it should_return_a_context_that_represents_the_specified_scope =
+            () => result.should_be_an_instance_of<Context>();
+
+        static IDictionary storage;
+        static IScopedStorage scope;
+        static IContext result;
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/CurrentThread.cs
@@ -0,0 +1,19 @@
+using System.Threading;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class CurrentThread : IThread
+    {
+        public T provide_slot_for<T>() where T : class, new()
+        {
+            var slot = Thread.GetNamedDataSlot(create_key_for<T>());
+            if (null == Thread.GetData(slot)) Thread.SetData(slot, new T());
+            return (T) Thread.GetData(slot);
+        }
+
+        string create_key_for<T>()
+        {
+            return Thread.CurrentThread.ManagedThreadId + GetType().FullName + typeof (T).FullName;
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IChangeTracker.cs
@@ -0,0 +1,17 @@
+using System;
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IChangeTracker : IDisposable
+    {
+        bool is_dirty();
+        void commit_to(IDatabase database);
+    }
+
+    public interface IChangeTracker<T> : IChangeTracker where T : IIdentifiable<Guid>
+    {
+        void register(T value);
+        void delete(T entity);
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IChangeTrackerFactory.cs
@@ -0,0 +1,10 @@
+using System;
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IChangeTrackerFactory
+    {
+        IChangeTracker<T> create_for<T>() where T : IIdentifiable<Guid>;
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IContext.cs
@@ -0,0 +1,10 @@
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IContext
+    {
+        bool contains<T>(IKey<T> key);
+        void add<T>(IKey<T> key, T value);
+        T value_for<T>(IKey<T> key);
+        void remove<T>(IKey<T> key);
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IDatabase.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IDatabase
+    {
+        IEnumerable<T> fetch_all<T>() where T : IIdentifiable<Guid>;
+        void apply(IStatement statement);
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IDatabaseConnection.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IDatabaseConnection : IDisposable
+    {
+        IEnumerable<T> query<T>();
+        IEnumerable<T> query<T>(Predicate<T> predicate);
+        void delete<T>(T entity);
+        void commit();
+        void store<T>(T entity);
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IdentityMapProxy.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class IdentityMapProxy<Key, Value> : IIdentityMap<Key, Value> where Value : IIdentifiable<Guid>
+    {
+        readonly IIdentityMap<Key, Value> real_map;
+        readonly IChangeTracker<Value> change_tracker;
+
+        public IdentityMapProxy(IChangeTracker<Value> change_tracker, IIdentityMap<Key, Value> real_map)
+        {
+            this.change_tracker = change_tracker;
+            this.real_map = real_map;
+        }
+
+        public IEnumerable<Value> all()
+        {
+            return real_map.all();
+        }
+
+        public void add(Key key, Value value)
+        {
+            change_tracker.register(value);
+            real_map.add(key, value);
+        }
+
+        public void update_the_item_for(Key key, Value new_value)
+        {
+            real_map.update_the_item_for(key, new_value);
+        }
+
+        public bool contains_an_item_for(Key key)
+        {
+            return real_map.contains_an_item_for(key);
+        }
+
+        public Value item_that_belongs_to(Key key)
+        {
+            return real_map.item_that_belongs_to(key);
+        }
+
+        public void evict(Key key)
+        {
+            change_tracker.delete(real_map.item_that_belongs_to(key));
+            real_map.evict(key);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IdentityMapSpecs.cs
@@ -0,0 +1,95 @@
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    [Concern(typeof (IdentityMap<,>))]
+    public class behaves_like_identity_map : concerns_for<IIdentityMap<int, string>, IdentityMap<int, string>>
+    {
+        public override IIdentityMap<int, string> create_sut()
+        {
+            return new IdentityMap<int, string>();
+        }
+    }
+
+    [Concern(typeof (IdentityMap<,>))]
+    public class when_getting_an_item_from_the_identity_map_for_an_item_that_has_been_added : behaves_like_identity_map
+    {
+        it should_return_the_item_that_was_added_for_the_given_key = () => result.should_be_equal_to("1");
+
+        because b = () =>
+                        {
+                            sut.add(1, "1");
+                            result = sut.item_that_belongs_to(1);
+                        };
+
+        static string result;
+    }
+
+    [Concern(typeof (IdentityMap<,>))]
+    public class when_getting_an_item_from_the_identity_map_that_has_not_been_added : behaves_like_identity_map
+    {
+        it should_return_the_default_value_for_that_type = () => result.should_be_equal_to(null);
+
+        because b = () => { result = sut.item_that_belongs_to(2); };
+
+        static string result;
+    }
+
+    [Concern(typeof (IdentityMap<,>))]
+    public class when_checking_if_an_item_has_been_added_to_the_identity_map_that_has_been_added :
+        behaves_like_identity_map
+    {
+        it should_return_true = () => result.should_be_true();
+
+        because b = () =>
+                        {
+                            sut.add(10, "10");
+                            result = sut.contains_an_item_for(10);
+                        };
+
+        static bool result;
+    }
+
+    [Concern(typeof (IdentityMap<,>))]
+    public class when_checking_if_an_item_has_been_added_to_the_identity_map_that_has_not_been_added :
+        behaves_like_identity_map
+    {
+        it should_return_false = () => result.should_be_false();
+
+        because b = () => { result = sut.contains_an_item_for(9); };
+
+        static bool result;
+    }
+
+    [Concern(typeof (IdentityMap<,>))]
+    public class when_updating_the_value_for_a_key_that_has_already_been_added_to_the_identity_map :
+        behaves_like_identity_map
+    {
+        it should_replace_the_old_item_with_the_new_one = () => result.should_be_equal_to("7");
+
+        because b = () =>
+                        {
+                            sut.add(6, "6");
+                            sut.update_the_item_for(6, "7");
+                            result = sut.item_that_belongs_to(6);
+                        };
+
+        static string result;
+    }
+
+    [Concern(typeof (IdentityMap<,>))]
+    public class when_updating_the_value_for_a_key_that_has_not_been_added_to_the_identity_map :
+        behaves_like_identity_map
+    {
+        it should_add_the_new_item = () => result.should_be_equal_to("3");
+
+        because b = () =>
+                        {
+                            sut.update_the_item_for(3, "3");
+                            result = sut.item_that_belongs_to(3);
+                        };
+
+        static string result;
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IIdentityMap.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IIdentityMap<TKey, TValue>
+    {
+        IEnumerable<TValue> all();
+        void add(TKey key, TValue value);
+        void update_the_item_for(TKey key, TValue new_value);
+        bool contains_an_item_for(TKey key);
+        TValue item_that_belongs_to(TKey key);
+        void evict(TKey key);
+    }
+
+    public class IdentityMap<TKey, TValue> : IIdentityMap<TKey, TValue>
+    {
+        readonly IDictionary<TKey, TValue> items_in_map;
+
+        public IdentityMap() : this(new Dictionary<TKey, TValue>())
+        {
+        }
+
+        public IdentityMap(IDictionary<TKey, TValue> items_in_map)
+        {
+            this.items_in_map = items_in_map;
+        }
+
+        public IEnumerable<TValue> all()
+        {
+            return items_in_map.Values;
+        }
+
+        public void add(TKey key, TValue value)
+        {
+            items_in_map.Add(key, value);
+        }
+
+        public void update_the_item_for(TKey key, TValue new_value)
+        {
+            if (contains_an_item_for(key)) items_in_map[key] = new_value;
+            else add(key, new_value);
+        }
+
+        public bool contains_an_item_for(TKey key)
+        {
+            return items_in_map.ContainsKey(key);
+        }
+
+        public TValue item_that_belongs_to(TKey key)
+        {
+            return contains_an_item_for(key) ? items_in_map[key] : default(TValue);
+        }
+
+        public void evict(TKey key)
+        {
+            if (contains_an_item_for(key)) items_in_map.Remove(key);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IKey.cs
@@ -0,0 +1,12 @@
+using System.Collections;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IKey<T>
+    {
+        bool is_found_in(IDictionary items);
+        T parse_from(IDictionary items);
+        void remove_from(IDictionary items);
+        void add_value_to(IDictionary items, T value);
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IScopedStorage.cs
@@ -0,0 +1,9 @@
+using System.Collections;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IScopedStorage
+    {
+        IDictionary provide_storage();
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IStatement.cs
@@ -0,0 +1,7 @@
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IStatement
+    {
+        void prepare(IDatabaseConnection connection);
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IStatementRegistry.cs
@@ -0,0 +1,11 @@
+using System;
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IStatementRegistry
+    {
+        IStatement prepare_delete_statement_for<T>(T entity) where T : IIdentifiable<Guid>;
+        IStatement prepare_command_for<T>(T entity) where T : IIdentifiable<Guid>;
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/IThread.cs
@@ -0,0 +1,7 @@
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface IThread
+    {
+        T provide_slot_for<T>() where T : class, new();
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/PerThread.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class PerThread : IContext
+    {
+        readonly IDictionary<int, LocalDataStoreSlot> slots;
+        readonly object mutex = new object();
+
+        public PerThread()
+        {
+            slots = new Dictionary<int, LocalDataStoreSlot>();
+        }
+
+        public bool contains<T>(IKey<T> key)
+        {
+            return key.is_found_in(get_items());
+        }
+
+        public void add<T>(IKey<T> key, T value)
+        {
+            key.add_value_to(get_items(), value);
+        }
+
+        public T value_for<T>(IKey<T> key)
+        {
+            return key.parse_from(get_items());
+        }
+
+        public void remove<T>(IKey<T> key)
+        {
+            key.remove_from(get_items());
+        }
+
+        IDictionary get_items()
+        {
+            var id = Thread.CurrentThread.ManagedThreadId;
+            within_lock(() =>
+                            {
+                                if (!slots.ContainsKey(id))
+                                {
+                                    var slot = Thread.GetNamedDataSlot(GetType().FullName);
+                                    slots.Add(id, slot);
+                                    Thread.SetData(slot, new Hashtable());
+                                }
+                            });
+            return (IDictionary) Thread.GetData(slots[id]);
+        }
+
+        void within_lock(Action action)
+        {
+            lock (mutex) action();
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/PerThreadScopedStorage.cs
@@ -0,0 +1,19 @@
+using System.Collections;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class PerThreadScopedStorage : IScopedStorage
+    {
+        readonly IThread current_thread;
+
+        public PerThreadScopedStorage(IThread current_thread)
+        {
+            this.current_thread = current_thread;
+        }
+
+        public IDictionary provide_storage()
+        {
+            return current_thread.provide_slot_for<Hashtable>();
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/PerThreadScopedStorageSpecs.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class PerThreadScopedStorageSpecs
+    {
+    }
+
+    [Concern(typeof (PerThreadScopedStorage))]
+    public class when_retrieving_the_storage_for_a_specific_thread :
+        concerns_for<IScopedStorage, PerThreadScopedStorage>
+    {
+        context c = () =>
+                        {
+                            thread_id = DateTime.Now.Ticks;
+                            thread = the_dependency<IThread>();
+                            storage = new Hashtable();
+                            when_the(thread)
+                                .is_told_to(x => x.provide_slot_for<Hashtable>())
+                                .it_will_return(storage);
+                        };
+
+        because b = () => { result = sut.provide_storage(); };
+
+        it should_return_the_storage_the_corresponds_to_the_current_thread = () => result.should_be_equal_to(storage);
+
+        static IDictionary result;
+        static IThread thread;
+        static long thread_id;
+        static Hashtable storage;
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/Session.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Gorilla.Commons.Infrastructure.Logging;
+using Gorilla.Commons.Utility.Core;
+using Gorilla.Commons.Utility.Extensions;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface ISession : IDisposable
+    {
+        T find<T>(Guid guid) where T : IIdentifiable<Guid>;
+        IEnumerable<T> all<T>() where T : IIdentifiable<Guid>;
+        void save<T>(T entity) where T : IIdentifiable<Guid>;
+        void delete<T>(T entity) where T : IIdentifiable<Guid>;
+        void flush();
+        bool is_dirty();
+    }
+
+    public class Session : ISession
+    {
+        ITransaction transaction;
+        readonly IDatabase database;
+        readonly IDictionary<Type, object> identity_maps;
+        long id;
+
+        public Session(ITransaction transaction, IDatabase database)
+        {
+            this.database = database;
+            this.transaction = transaction;
+            identity_maps = new Dictionary<Type, object>();
+            id = DateTime.Now.Ticks;
+        }
+
+        public T find<T>(Guid id) where T : IIdentifiable<Guid>
+        {
+            if (get_identity_map_for<T>().contains_an_item_for(id))
+            {
+                return get_identity_map_for<T>().item_that_belongs_to(id);
+            }
+
+            var entity = database.fetch_all<T>().Single(x => x.id.Equals(id));
+            get_identity_map_for<T>().add(id, entity);
+            return entity;
+        }
+
+        public IEnumerable<T> all<T>() where T : IIdentifiable<Guid>
+        {
+            database
+                .fetch_all<T>()
+                .where(x => !get_identity_map_for<T>().contains_an_item_for(x.id))
+                .each(x => get_identity_map_for<T>().add(x.id, x));
+            return get_identity_map_for<T>().all();
+        }
+
+        public void save<T>(T entity) where T : IIdentifiable<Guid>
+        {
+            this.log().debug("saving {0}: {1}", id, entity);
+            get_identity_map_for<T>().add(entity.id, entity);
+        }
+
+        public void delete<T>(T entity) where T : IIdentifiable<Guid>
+        {
+            get_identity_map_for<T>().evict(entity.id);
+        }
+
+        public void flush()
+        {
+            this.log().debug("flushing session {0}", id);
+            transaction.commit_changes();
+            transaction = null;
+        }
+
+        public bool is_dirty()
+        {
+            this.log().debug("is dirty? {0}", id);
+            return null != transaction && transaction.is_dirty();
+        }
+
+        public void Dispose()
+        {
+            if (null != transaction) transaction.rollback_changes();
+        }
+
+        IIdentityMap<Guid, T> get_identity_map_for<T>() where T : IIdentifiable<Guid>
+        {
+            return identity_maps.ContainsKey(typeof (T))
+                       ? identity_maps[typeof (T)].downcast_to<IIdentityMap<Guid, T>>()
+                       : create_map_for<T>();
+        }
+
+        IIdentityMap<Guid, T> create_map_for<T>() where T : IIdentifiable<Guid>
+        {
+            var identity_map = transaction.create_for<T>();
+            identity_maps.Add(typeof (T), identity_map);
+            return identity_map;
+        }
+
+        public override string ToString()
+        {
+            return "session: {0}".formatted_using(id);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/SessionFactory.cs
@@ -0,0 +1,25 @@
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface ISessionFactory : IFactory<ISession>
+    {
+    }
+
+    public class SessionFactory : ISessionFactory
+    {
+        readonly IDatabase database;
+        readonly IChangeTrackerFactory factory;
+
+        public SessionFactory(IDatabase database, IChangeTrackerFactory factory)
+        {
+            this.database = database;
+            this.factory = factory;
+        }
+
+        public ISession create()
+        {
+            return new Session(new Transaction(database, factory), database);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/SessionFactorySpecs.cs
@@ -0,0 +1,19 @@
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class SessionFactorySpecs
+    {
+    }
+
+    [Concern(typeof (SessionFactory))]
+    public class when_creating_a_new_session : concerns_for<ISessionFactory, SessionFactory>
+    {
+        it should_return_a_new_session = () => result.should_not_be_null();
+
+        because b = () => { result = sut.create(); };
+
+        static ISession result;
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/SessionNotStartedException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class SessionNotStartedException : Exception
+    {
+        public SessionNotStartedException() : base("A session could not be found. Did you forget to open a session?")
+        {
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/SessionProvider.cs
@@ -0,0 +1,25 @@
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface ISessionProvider
+    {
+        ISession get_the_current_session();
+    }
+
+    public class SessionProvider : ISessionProvider
+    {
+        readonly IContext context;
+        readonly IKey<ISession> session_key;
+
+        public SessionProvider(IContext context, IKey<ISession> session_key)
+        {
+            this.context = context;
+            this.session_key = session_key;
+        }
+
+        public ISession get_the_current_session()
+        {
+            if (!context.contains(session_key)) throw new SessionNotStartedException();
+            return context.value_for(session_key);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/SessionSpecs.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Collections.Generic;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class SessionSpecs
+    {
+    }
+
+    public class behaves_like_session : concerns_for<ISession, Session>
+    {
+        context c = () =>
+                        {
+                            transaction = the_dependency<ITransaction>();
+                            database = the_dependency<IDatabase>();
+                        };
+
+        static protected ITransaction transaction;
+        static protected IDatabase database;
+    }
+
+    [Concern(typeof (Session))]
+    public class when_saving_a_transient_item_to_a_session : behaves_like_session
+    {
+        it should_add_the_entity_to_the_identity_map = () => map.was_told_to(x => x.add(guid, entity));
+
+        context c = () =>
+                        {
+                            guid = Guid.NewGuid();
+                            entity = an<ITestEntity>();
+                            map = an<IIdentityMap<Guid, ITestEntity>>();
+
+                            when_the(entity).is_told_to(x => x.id).it_will_return(guid);
+                            when_the(transaction).is_told_to(x => x.create_for<ITestEntity>()).it_will_return(map);
+                        };
+
+        because b = () => sut.save(entity);
+
+        static ITestEntity entity;
+        static IIdentityMap<Guid, ITestEntity> map;
+        static Id<Guid> guid;
+    }
+
+    [Concern(typeof (Session))]
+    public class when_commiting_the_changes_made_in_a_session : behaves_like_session
+    {
+        it should_commit_all_the_changes_from_the_running_transaction =
+            () => transaction.was_told_to(x => x.commit_changes());
+
+        it should_not_rollback_any_changes_from_the_running_transaction =
+            () => transaction.was_not_told_to(x => x.rollback_changes());
+
+        because b = () =>
+                        {
+                            sut.flush();
+                            sut.Dispose();
+                        };
+    }
+
+    [Concern(typeof (Session))]
+    public class when_closing_a_session_before_flushing_the_changes : behaves_like_session
+    {
+        it should_rollback_any_changes_made_in_the_current_transaction =
+            () => transaction.was_told_to(x => x.rollback_changes());
+
+        because b = () => sut.Dispose();
+    }
+
+    [Concern(typeof (Session))]
+    public class when_loading_all_instances_of_a_certain_type_and_some_have_already_been_loaded : behaves_like_session
+    {
+        it should_return_the_items_from_the_cache = () => results.should_contain(cached_item);
+
+        it should_exclude_duplicates_from_the_database = () => results.should_not_contain(database_item);
+
+        it should_add_items_from_the_database_to_the_identity_map =
+            () => identity_map.was_told_to(x => x.add(id_of_the_uncached_item, uncached_item));
+
+        context c = () =>
+                        {
+                            id = Guid.NewGuid();
+                            id_of_the_uncached_item = Guid.NewGuid();
+                            identity_map = an<IIdentityMap<Guid, ITestEntity>>();
+                            cached_item = an<ITestEntity>();
+                            database_item = an<ITestEntity>();
+                            uncached_item = an<ITestEntity>();
+
+                            when_the(cached_item).is_told_to(x => x.id).it_will_return(id);
+                            when_the(database_item).is_told_to(x => x.id).it_will_return(id);
+                            when_the(uncached_item).is_told_to(x => x.id).it_will_return(id_of_the_uncached_item);
+                            when_the(transaction).is_told_to(x => x.create_for<ITestEntity>()).it_will_return( identity_map);
+                            when_the(identity_map).is_told_to(x => x.contains_an_item_for(id)).it_will_return(true);
+                            when_the(identity_map).is_told_to(x => x.all()).it_will_return(cached_item);
+                            when_the(database).is_told_to(x => x.fetch_all<ITestEntity>())
+                                .it_will_return(database_item, uncached_item);
+                        };
+
+        because b = () =>
+                        {
+                            sut.find<ITestEntity>(id);
+                            results = sut.all<ITestEntity>();
+                        };
+
+        static IEnumerable<ITestEntity> results;
+        static Id<Guid> id;
+        static Id<Guid> id_of_the_uncached_item;
+        static ITestEntity cached_item;
+        static ITestEntity database_item;
+        static IIdentityMap<Guid, ITestEntity> identity_map;
+        static ITestEntity uncached_item;
+    }
+
+    [Concern(typeof (Session))]
+    public class when_looking_up_a_specific_entity_by_its_id_and_it_has_not_been_loaded_into_the_cache :
+        behaves_like_session
+    {
+        it should_return_that_item = () => { result.should_be_equal_to(correct_item); };
+
+        it should_add_that_item_to_the_identity_map = () => map.was_told_to(x => x.add(id, correct_item));
+
+        context c = () =>
+                        {
+                            id = Guid.NewGuid();
+                            wrong_item = an<ITestEntity>();
+                            correct_item = an<ITestEntity>();
+                            map = an<IIdentityMap<Guid, ITestEntity>>();
+                            when_the(wrong_item).is_told_to(x => x.id).it_will_return<Id<Guid>>(Guid.NewGuid());
+                            when_the(correct_item).is_told_to(x => x.id).it_will_return(id);
+                            when_the(database)
+                                .is_told_to(x => x.fetch_all<ITestEntity>())
+                                .it_will_return(wrong_item, correct_item);
+                            when_the(transaction).is_told_to(x => x.create_for<ITestEntity>())
+                                .it_will_return(map);
+                        };
+
+        because b = () => { result = sut.find<ITestEntity>(id); };
+
+        static Id<Guid> id;
+        static IIdentifiable<Guid> result;
+        static ITestEntity correct_item;
+        static ITestEntity wrong_item;
+        static IIdentityMap<Guid, ITestEntity> map;
+    }
+
+    [Concern(typeof (Session))]
+    public class when_deleting_an_item_from_the_database : behaves_like_session
+    {
+        it should_remove_that_item_from_the_cache = () => map.was_told_to(x => x.evict(id));
+
+        context c = () =>
+                        {
+                            id = Guid.NewGuid();
+                            entity = an<ITestEntity>();
+                            map = an<IIdentityMap<Guid, ITestEntity>>();
+
+                            when_the(entity).is_told_to(x => x.id).it_will_return(id);
+                            when_the(transaction).is_told_to(x => x.create_for<ITestEntity>()).it_will_return(map);
+                            when_the(database).is_told_to(x => x.fetch_all<ITestEntity>()).it_will_return(entity);
+                        };
+
+        because b = () =>
+                        {
+                            sut.find<ITestEntity>(id);
+                            sut.delete(entity);
+                        };
+
+        static Id<Guid> id;
+        static IIdentityMap<Guid, ITestEntity> map;
+        static ITestEntity entity;
+    }
+
+    public interface ITestEntity : IIdentifiable<Guid>
+    {
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/SingletonScopedStorage.cs
@@ -0,0 +1,14 @@
+using System.Collections;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class SingletonScopedStorage : IScopedStorage
+    {
+        static readonly IDictionary storage = new Hashtable();
+
+        public IDictionary provide_storage()
+        {
+            return storage;
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/StatementRegistry.cs
@@ -0,0 +1,48 @@
+using System;
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class StatementRegistry : IStatementRegistry
+    {
+        public IStatement prepare_delete_statement_for<T>(T entity) where T : IIdentifiable<Guid>
+        {
+            return new DeletionStatement<T>(entity);
+        }
+
+        public IStatement prepare_command_for<T>(T entity) where T : IIdentifiable<Guid>
+        {
+            return new SaveOrUpdateStatement<T>(entity);
+        }
+    }
+
+    public class SaveOrUpdateStatement<T> : IStatement where T : IIdentifiable<Guid>
+    {
+        readonly T entity;
+
+        public SaveOrUpdateStatement(T entity)
+        {
+            this.entity = entity;
+        }
+
+        public void prepare(IDatabaseConnection connection)
+        {
+            connection.store(entity);
+        }
+    }
+
+    public class DeletionStatement<T> : IStatement
+    {
+        readonly T entity;
+
+        public DeletionStatement(T entity)
+        {
+            this.entity = entity;
+        }
+
+        public void prepare(IDatabaseConnection connection)
+        {
+            connection.delete(entity);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/TrackerEntry.cs
@@ -0,0 +1,47 @@
+using System.Reflection;
+using Gorilla.Commons.Infrastructure.Logging;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface ITrackerEntry<T>
+    {
+        T current { get; }
+        bool has_changes();
+    }
+
+    public class TrackerEntry<T> : ITrackerEntry<T>
+    {
+        readonly T original;
+
+        public TrackerEntry(T original, T current)
+        {
+            this.original = original;
+            this.current = current;
+        }
+
+        public T current { get; set; }
+
+        public bool has_changes()
+        {
+            this.log().debug("checking for changes");
+            var type = original.GetType();
+            foreach (var field in type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
+            {
+                var original_value = field.GetValue(original);
+                var current_value = field.GetValue(current);
+                if (original_value == null && current_value != null)
+                {
+                    this.log().debug("{0} has changes: {1}", field, original);
+                    return true;
+                }
+                if (original_value != null && !original_value.Equals(current_value))
+                {
+                    this.log().debug("{0} has changes: {1}", field, original);
+                    return true;
+                }
+            }
+            this.log().debug("has no changes: {0}", original);
+            return false;
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/TrackerEntryMapper.cs
@@ -0,0 +1,29 @@
+using Gorilla.Commons.Infrastructure.Cloning;
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface ITrackerEntryMapper<T> : IMapper<T, ITrackerEntry<T>>
+    {
+    }
+
+    public class TrackerEntryMapper<T> : ITrackerEntryMapper<T>
+    {
+        readonly IPrototype prototype;
+
+        public TrackerEntryMapper(IPrototype prototype)
+        {
+            this.prototype = prototype;
+        }
+
+        public ITrackerEntry<T> map_from(T item)
+        {
+            return new TrackerEntry<T>(create_prototype(item), item);
+        }
+
+        T create_prototype(T item)
+        {
+            return prototype.clone(item);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/TrackerEntrySpecs.cs
@@ -0,0 +1,140 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using Gorilla.Commons.Utility.Core;
+using Gorilla.Commons.Utility.Extensions;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class TrackerEntrySpecs
+    {
+    }
+
+    public abstract class behaves_like_tracker_entry : concerns_for<ITrackerEntry<Pillow>>
+    {
+    }
+
+    [Concern(typeof (ITrackerEntry<>))]
+    public class when_comparing_the_current_instance_of_a_component_with_its_original_and_it_has_changes :
+        behaves_like_tracker_entry
+    {
+        it should_indicate_that_there_are_changes = () => result.should_be_true();
+
+        because b = () => { result = sut.has_changes(); };
+
+        public override ITrackerEntry<Pillow> create_sut()
+        {
+            return new TrackerEntry<Pillow>(new Pillow("pink"), new Pillow("yellow"));
+        }
+
+        static bool result;
+    }
+
+    [Concern(typeof (ITrackerEntry<>))]
+    public class when_the_original_instance_has_a_null_field_that_is_now_not_null :
+        behaves_like_tracker_entry
+    {
+        it should_indicate_that_there_are_changes = () => result.should_be_true();
+
+        because b = () => { result = sut.has_changes(); };
+
+        public override ITrackerEntry<Pillow> create_sut()
+        {
+            return new TrackerEntry<Pillow>(new Pillow(null), new Pillow("yellow"));
+        }
+
+        static bool result;
+    }
+
+    [Concern(typeof (ITrackerEntry<>))]
+    public class when_the_original_instance_had_a_non_null_field_and_the_current_instance_has_a_null_field :
+        behaves_like_tracker_entry
+    {
+        it should_indicate_that_there_are_changes = () => result.should_be_true();
+
+        because b = () => { result = sut.has_changes(); };
+
+        context c = () =>
+                        {
+                            var id = Guid.NewGuid();
+                            original = new Pillow("green", id);
+                            current = new Pillow(null, id);
+                        };
+
+        public override ITrackerEntry<Pillow> create_sut()
+        {
+            return new TrackerEntry<Pillow>(original, current);
+        }
+
+        static bool result;
+        static Pillow original;
+        static Pillow current;
+    }
+
+    [Concern(typeof (ITrackerEntry<>))]
+    public class when_the_original_instance_has_the_same_value_as_the_current_instance :
+        behaves_like_tracker_entry
+    {
+        it should_indicate_that_there_are_no_changes = () => result.should_be_false();
+
+        because b = () => { result = sut.has_changes(); };
+
+        context c = () =>
+                        {
+                            var id = Guid.NewGuid();
+                            original = new Pillow("green", id);
+                            current = new Pillow("green", id);
+                        };
+
+        public override ITrackerEntry<Pillow> create_sut()
+        {
+            return new TrackerEntry<Pillow>(original, current);
+        }
+
+        static bool result;
+        static Pillow original;
+        static Pillow current;
+    }
+
+    public class Pillow : IIdentifiable<Guid>
+    {
+        readonly string color;
+
+        public Pillow(string color) : this(color, Guid.NewGuid())
+        {
+        }
+
+        public Pillow(string color, Guid id)
+        {
+            this.color = color;
+            this.id = id;
+        }
+
+        public Id<Guid> id { get; set; }
+
+        public bool Equals(Pillow other)
+        {
+            if (ReferenceEquals(null, other)) return false;
+            if (ReferenceEquals(this, other)) return true;
+            return other.id.Equals(id);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            if (ReferenceEquals(this, obj)) return true;
+            if (obj.GetType() != typeof (Pillow)) return false;
+            return Equals((Pillow) obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return id.GetHashCode();
+        }
+
+        public override string ToString()
+        {
+            return "{0} id: {1}".formatted_using(base.ToString(), id);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/Transaction.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Gorilla.Commons.Utility.Core;
+using Gorilla.Commons.Utility.Extensions;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public interface ITransaction
+    {
+        IIdentityMap<Guid, T> create_for<T>() where T : IIdentifiable<Guid>;
+        void commit_changes();
+        void rollback_changes();
+        bool is_dirty();
+    }
+
+    public class Transaction : ITransaction
+    {
+        readonly IDatabase database;
+        readonly IChangeTrackerFactory factory;
+        readonly IDictionary<Type, IChangeTracker> change_trackers;
+
+        public Transaction(IDatabase database, IChangeTrackerFactory factory)
+        {
+            this.factory = factory;
+            this.database = database;
+            change_trackers = new Dictionary<Type, IChangeTracker>();
+        }
+
+        public IIdentityMap<Guid, T> create_for<T>() where T : IIdentifiable<Guid>
+        {
+            return new IdentityMapProxy<Guid, T>(get_change_tracker_for<T>(), new IdentityMap<Guid, T>());
+        }
+
+        public void commit_changes()
+        {
+            change_trackers.Values.where(x => x.is_dirty()).each(x => x.commit_to(database));
+        }
+
+        public void rollback_changes()
+        {
+            change_trackers.each(x => x.Value.Dispose());
+            change_trackers.Clear();
+        }
+
+        public bool is_dirty()
+        {
+            return change_trackers.Values.Count(x => x.is_dirty()) > 0;
+        }
+
+        IChangeTracker<T> get_change_tracker_for<T>() where T : IIdentifiable<Guid>
+        {
+            if (!change_trackers.ContainsKey(typeof (T))) change_trackers.Add(typeof (T), factory.create_for<T>());
+            return change_trackers[typeof (T)].downcast_to<IChangeTracker<T>>();
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/TransactionSpecs.cs
@@ -0,0 +1,113 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using Gorilla.Commons.Utility.Core;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class TransactionSpecs
+    {
+    }
+
+    [Concern(typeof (Transaction))]
+    public class behaves_like_transaction : concerns_for<ITransaction, Transaction>
+    {
+        context c = () =>
+                        {
+                            database = the_dependency<IDatabase>();
+                            factory = the_dependency<IChangeTrackerFactory>();
+                        };
+
+        static protected IDatabase database;
+        static protected IChangeTrackerFactory factory;
+    }
+
+    [Concern(typeof (Transaction))]
+    public class when_creating_an_identity_map_for_a_specific_entity : behaves_like_transaction
+    {
+        it should_return_a_new_identity_map = () => result.should_not_be_null();
+
+        because b = () => { result = sut.create_for<IIdentifiable<Guid>>(); };
+
+        static IIdentityMap<Guid, IIdentifiable<Guid>> result;
+    }
+
+    [Concern(typeof (Transaction))]
+    public class when_committing_a_transaction_and_an_item_in_the_identity_map_has_changed : behaves_like_transaction
+    {
+        it should_commit_the_changes_to_that_item =
+            () => tracker.was_told_to<IChangeTracker<IMovie>>(x => x.commit_to(database));
+
+        context c = () =>
+                        {
+                            movie = new Movie("Goldeneye");
+                            tracker = an<IChangeTracker<IMovie>>();
+
+                            when_the(factory).is_told_to(x => x.create_for<IMovie>()).it_will_return(tracker);
+                            when_the(tracker).is_told_to(x => x.is_dirty()).it_will_return(true);
+                        };
+
+
+        because b = () =>
+                        {
+                            sut.create_for<IMovie>().add(movie.id, movie);
+                            movie.change_name_to("Austin Powers");
+                            sut.commit_changes();
+                        };
+
+        static IMovie movie;
+        static IChangeTracker<IMovie> tracker;
+    }
+
+    [Concern(typeof (Transaction))]
+    public class when_deleting_a_set_of_entities_from_the_database : behaves_like_transaction
+    {
+        it should_prepare_to_delete_that_item_form_the_database = () => tracker.was_told_to(x => x.delete(movie));
+
+        it should_delete_all_items_marked_for_deletion = () => tracker.was_told_to(x => x.commit_to(database));
+
+        context c = () =>
+                        {
+                            movie = new Movie("Goldeneye");
+                            tracker = an<IChangeTracker<IMovie>>();
+
+                            when_the(factory).is_told_to(x => x.create_for<IMovie>()).it_will_return(tracker);
+                            when_the(tracker).is_told_to(x => x.is_dirty()).it_will_return(true);
+                        };
+
+        because b = () =>
+                        {
+                            var map = sut.create_for<IMovie>();
+                            map.add(movie.id, movie);
+                            map.evict(movie.id);
+                            sut.commit_changes();
+                        };
+
+        static IMovie movie;
+        static IChangeTracker<IMovie> tracker;
+    }
+
+    public interface IMovie : IIdentifiable<Guid>
+    {
+        string name { get; }
+        void change_name_to(string name);
+    }
+
+    internal class Movie : IMovie
+    {
+        public Movie(string name)
+        {
+            id = Guid.NewGuid();
+            this.name = name;
+        }
+
+        public string name { get; set; }
+
+        public void change_name_to(string new_name)
+        {
+            name = new_name;
+        }
+
+        public Id<Guid> id { get; set; }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/Transactions/TypedKey.cs
@@ -0,0 +1,50 @@
+using System.Collections;
+
+namespace Gorilla.Commons.Infrastructure.Transactions
+{
+    public class TypedKey<T> : IKey<T>
+    {
+        public bool is_found_in(IDictionary items)
+        {
+            return items.Contains(create_key());
+        }
+
+        public T parse_from(IDictionary items)
+        {
+            return (T) items[create_key()];
+        }
+
+        public void remove_from(IDictionary items)
+        {
+            if (is_found_in(items)) items.Remove(create_key());
+        }
+
+        public void add_value_to(IDictionary items, T value)
+        {
+            items[create_key()] = value;
+        }
+
+        public bool Equals(TypedKey<T> obj)
+        {
+            return !ReferenceEquals(null, obj);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            if (ReferenceEquals(this, obj)) return true;
+            if (obj.GetType() != typeof (TypedKey<T>)) return false;
+            return Equals((TypedKey<T>) obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return GetType().GetHashCode();
+        }
+
+        string create_key()
+        {
+            return GetType().FullName;
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.DataAccess/MoMoney.DataAccess.csproj
@@ -93,6 +93,44 @@
     <Compile Include="Db40\DatabaseConnection.cs" />
     <Compile Include="IConnectionFactory.cs" />
     <Compile Include="IDatabaseConfiguration.cs" />
+    <Compile Include="Transactions\ChangeTracker.cs" />
+    <Compile Include="Transactions\ChangeTrackerFactory.cs" />
+    <Compile Include="Transactions\ChangeTrackerFactorySpecs.cs" />
+    <Compile Include="Transactions\ChangeTrackerSpecs.cs" />
+    <Compile Include="Transactions\Context.cs" />
+    <Compile Include="Transactions\ContextFactory.cs" />
+    <Compile Include="Transactions\ContextFactorySpecs.cs" />
+    <Compile Include="Transactions\CurrentThread.cs" />
+    <Compile Include="Transactions\IChangeTracker.cs" />
+    <Compile Include="Transactions\IChangeTrackerFactory.cs" />
+    <Compile Include="Transactions\IContext.cs" />
+    <Compile Include="Transactions\IDatabase.cs" />
+    <Compile Include="Transactions\IDatabaseConnection.cs" />
+    <Compile Include="Transactions\IdentityMapProxy.cs" />
+    <Compile Include="Transactions\IdentityMapSpecs.cs" />
+    <Compile Include="Transactions\IIdentityMap.cs" />
+    <Compile Include="Transactions\IKey.cs" />
+    <Compile Include="Transactions\IScopedStorage.cs" />
+    <Compile Include="Transactions\IStatement.cs" />
+    <Compile Include="Transactions\IStatementRegistry.cs" />
+    <Compile Include="Transactions\IThread.cs" />
+    <Compile Include="Transactions\PerThread.cs" />
+    <Compile Include="Transactions\PerThreadScopedStorage.cs" />
+    <Compile Include="Transactions\PerThreadScopedStorageSpecs.cs" />
+    <Compile Include="Transactions\Session.cs" />
+    <Compile Include="Transactions\SessionFactory.cs" />
+    <Compile Include="Transactions\SessionFactorySpecs.cs" />
+    <Compile Include="Transactions\SessionNotStartedException.cs" />
+    <Compile Include="Transactions\SessionProvider.cs" />
+    <Compile Include="Transactions\SessionSpecs.cs" />
+    <Compile Include="Transactions\SingletonScopedStorage.cs" />
+    <Compile Include="Transactions\StatementRegistry.cs" />
+    <Compile Include="Transactions\TrackerEntry.cs" />
+    <Compile Include="Transactions\TrackerEntryMapper.cs" />
+    <Compile Include="Transactions\TrackerEntrySpecs.cs" />
+    <Compile Include="Transactions\Transaction.cs" />
+    <Compile Include="Transactions\TransactionSpecs.cs" />
+    <Compile Include="Transactions\TypedKey.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MoMoney.Domain\MoMoney.Domain.csproj">
product/MoMoney.Domain/Core/Entity.cs
@@ -16,7 +16,7 @@ namespace MoMoney.Domain.Core
             id = Guid.NewGuid();
         }
 
-        public Guid id { get; private set; }
+        public Id<Guid> id { get; private set; }
 
         public bool Equals(Entity<T> obj)
         {
product/MoMoney.Presentation/Model/Projects/ProjectController.cs
@@ -1,11 +1,11 @@
 using Gorilla.Commons.Infrastructure.Eventing;
 using Gorilla.Commons.Infrastructure.FileSystem;
 using Gorilla.Commons.Infrastructure.Logging;
-using Gorilla.Commons.Infrastructure.Transactions;
 using Gorilla.Commons.Utility.Core;
 using Gorilla.Commons.Utility.Extensions;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Service.Infrastructure;
+using MoMoney.Service.Infrastructure.Transactions;
 
 namespace MoMoney.Presentation.Model.Projects
 {
product/MoMoney.Presentation/Model/Projects/ProjectControllerSpecs.cs
@@ -2,11 +2,11 @@ using System;
 using developwithpassion.bdd.contexts;
 using Gorilla.Commons.Infrastructure.Eventing;
 using Gorilla.Commons.Infrastructure.FileSystem;
-using Gorilla.Commons.Infrastructure.Transactions;
 using Gorilla.Commons.Testing;
 using Gorilla.Commons.Utility.Extensions;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Service.Infrastructure;
+using MoMoney.Service.Infrastructure.Transactions;
 
 namespace MoMoney.Presentation.Model.Projects
 {
product/MoMoney.Presentation/Presenters/Shell/NotificationIconPresenter.cs
@@ -1,6 +1,5 @@
 using System.Net.NetworkInformation;
 using Gorilla.Commons.Infrastructure.Eventing;
-using MoMoney.Modules.Core;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Resources;
 using MoMoney.Presentation.Views.Shell;
product/MoMoney.Presentation/Presenters/Shell/StatusBarPresenter.cs
@@ -1,7 +1,6 @@
 using Gorilla.Commons.Infrastructure.Eventing;
 using Gorilla.Commons.Utility;
 using Gorilla.Commons.Utility.Extensions;
-using MoMoney.Modules.Core;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Resources;
 using MoMoney.Presentation.Views.Shell;
product/MoMoney.Presentation/Presenters/Shell/TaskTrayPresenter.cs
@@ -1,5 +1,4 @@
 using Gorilla.Commons.Infrastructure.Eventing;
-using MoMoney.Modules.Core;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Views.Shell;
 
product/MoMoney.Presentation/Presenters/Shell/TitleBarPresenter.cs
@@ -1,6 +1,5 @@
 using Gorilla.Commons.Infrastructure.Eventing;
 using Gorilla.Commons.Infrastructure.Logging;
-using MoMoney.Modules.Core;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Model.Projects;
 using MoMoney.Presentation.Views.Shell;
product/MoMoney.Presentation/Presenters/Shell/UnhandledErrorPresenter.cs
@@ -1,5 +1,4 @@
 using Gorilla.Commons.Infrastructure.Eventing;
-using MoMoney.Modules.Core;
 using MoMoney.Presentation.Core;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Presenters.Commands;
product/MoMoney.Presentation/Views/AddCompanyView.cs
@@ -42,8 +42,8 @@ namespace MoMoney.Presentation.Views
         {
             ux_companys_listing.DataSource = companies.databind();
 
-            listView1.Items.Clear();
-            listView1.Items.AddRange(companies.Select(x => new ListViewItem(x.name, 0)).ToArray());
+            //listView1.Items.Clear();
+            //listView1.Items.AddRange(companies.Select(x => new ListViewItem(x.name, 0)).ToArray());
         }
     }
 }
\ No newline at end of file
product/MoMoney.Presentation/IModule.cs
@@ -1,6 +1,6 @@
 using Gorilla.Commons.Utility.Core;
 
-namespace MoMoney.Modules.Core
+namespace MoMoney.Presentation
 {
     public interface IModule : ICommand
     {
product/MoMoney.Service/Application/Public/AddNewIncomeCommand.svc
@@ -1,1 +0,0 @@
-๏ปฟ<%@ ServiceHost Language="C#" Service="MoMoney.Service.Application.AddNewIncomeCommand" CodeBehind="MoMoney.Service.Application.AddNewIncomeCommand.cs" />
\ No newline at end of file
product/MoMoney.Service/Application/AddNewIncomeCommandSpecs.cs
@@ -2,6 +2,7 @@ using System;
 using developwithpassion.bdd.contexts;
 using Gorilla.Commons.Testing;
 using Gorilla.Commons.Utility;
+using Gorilla.Commons.Utility.Core;
 using MoMoney.Domain.Accounting;
 using MoMoney.Domain.Core;
 using MoMoney.Domain.repositories;
@@ -41,7 +42,7 @@ namespace MoMoney.Service.Application
                             var a_company = an<ICompany>();
                             var matching_income = an<IIncome>();
                             var today = new Date(2008, 12, 26);
-                            var id = Guid.NewGuid();
+                            Id<Guid> id = Guid.NewGuid();
 
                             income = new IncomeSubmissionDto
                                          {
product/MoMoney.Service/Infrastructure/Transactions/EmptyUnitOfWork.cs
@@ -0,0 +1,21 @@
+using Gorilla.Commons.Infrastructure.Logging;
+
+namespace MoMoney.Service.Infrastructure.Transactions
+{
+    public class EmptyUnitOfWork : IUnitOfWork
+    {
+        public void commit()
+        {
+            this.log().debug("committed empty unit of work");
+        }
+
+        public bool is_dirty()
+        {
+            return false;
+        }
+
+        public void Dispose()
+        {
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.Service/Infrastructure/Transactions/UnitOfWork.cs
@@ -0,0 +1,41 @@
+using System;
+using Gorilla.Commons.Infrastructure.Transactions;
+
+namespace MoMoney.Service.Infrastructure.Transactions
+{
+    public interface IUnitOfWork : IDisposable
+    {
+        void commit();
+        bool is_dirty();
+    }
+
+    public class UnitOfWork : IUnitOfWork
+    {
+        readonly ISession session;
+        readonly IContext context;
+        readonly IKey<ISession> key;
+
+        public UnitOfWork(ISession session, IContext context, IKey<ISession> key)
+        {
+            this.session = session;
+            this.context = context;
+            this.key = key;
+        }
+
+        public void commit()
+        {
+            if (is_dirty()) session.flush();
+        }
+
+        public bool is_dirty()
+        {
+            return session.is_dirty();
+        }
+
+        public void Dispose()
+        {
+            context.remove(key);
+            session.Dispose();
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.Service/Infrastructure/Transactions/UnitOfWorkFactory.cs
@@ -0,0 +1,40 @@
+using Gorilla.Commons.Infrastructure.Transactions;
+using Gorilla.Commons.Utility.Core;
+
+namespace MoMoney.Service.Infrastructure.Transactions
+{
+    public interface IUnitOfWorkFactory : IFactory<IUnitOfWork>
+    {
+    }
+
+    public class UnitOfWorkFactory : IUnitOfWorkFactory
+    {
+        readonly IContext context;
+        readonly ISessionFactory factory;
+        readonly IKey<ISession> key;
+
+        public UnitOfWorkFactory(IContext context, ISessionFactory factory, IKey<ISession> key)
+        {
+            this.context = context;
+            this.key = key;
+            this.factory = factory;
+        }
+
+        public IUnitOfWork create()
+        {
+            return unit_of_work_been_started() ? new EmptyUnitOfWork() : start_a_new_unit_of_work();
+        }
+
+        bool unit_of_work_been_started()
+        {
+            return context.contains(key);
+        }
+
+        IUnitOfWork start_a_new_unit_of_work()
+        {
+            var session = factory.create();
+            context.add(key, session);
+            return new UnitOfWork(session, context, key);
+        }
+    }
+}
\ No newline at end of file
product/MoMoney.Service/Infrastructure/Transactions/UnitOfWorkFactorySpecs.cs
@@ -0,0 +1,78 @@
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Infrastructure.Transactions;
+using Gorilla.Commons.Testing;
+
+namespace MoMoney.Service.Infrastructure.Transactions
+{
+    public class UnitOfWorkFactorySpecs
+    {
+    }
+
+    [Concern(typeof (UnitOfWorkFactory))]
+    public abstract class concerns_for_unit_of_work_factory : concerns_for<IUnitOfWorkFactory, UnitOfWorkFactory>
+    {
+        context c = () =>
+                        {
+                            session_context = the_dependency<IContext>();
+                            factory = the_dependency<ISessionFactory>();
+                            key = the_dependency<IKey<ISession>>();
+                        };
+
+        static protected IContext session_context;
+        static protected ISessionFactory factory;
+        static protected IKey<ISession> key;
+    }
+
+    [Concern(typeof (UnitOfWorkFactory))]
+    public class when_a_unit_of_work_has_not_been_started : concerns_for_unit_of_work_factory
+    {
+        context c = () => { when_the(session_context).is_told_to(x => x.contains(key)).it_will_return(false); };
+    }
+
+    [Concern(typeof (UnitOfWorkFactory))]
+    public class when_a_unit_of_work_has_been_started : concerns_for_unit_of_work_factory
+    {
+        context c = () => { when_the(session_context).is_told_to(x => x.contains(key)).it_will_return(true); };
+    }
+
+    [Concern(typeof (UnitOfWorkFactory))]
+    public class when_creating_a_new_unit_of_work : when_a_unit_of_work_has_not_been_started
+    {
+        context c = () =>
+                        {
+                            session = an<ISession>();
+                            when_the(factory).is_told_to(x => x.create()).it_will_return(session);
+                        };
+
+        because b = () => { result = sut.create(); };
+
+        it should_create_a_new_unit_of_work = () => factory.was_told_to(x => x.create());
+
+        it should_add_the_session_to_the_current_context = () => session_context.was_told_to(x => x.add(key, session));
+
+        it should_return_a_brand_new_unit_of_work = () =>
+                                                        {
+                                                            result.should_not_be_null();
+                                                            result.should_be_an_instance_of<UnitOfWork>();
+                                                        };
+
+        static IUnitOfWork result;
+        static ISession session;
+    }
+
+    [Concern(typeof (UnitOfWorkFactory))]
+    public class when_attempting_to_create_a_new_unit_of_work : when_a_unit_of_work_has_been_started
+    {
+        because b = () => { result = sut.create(); };
+
+        it should_not_create_a_new_unit_of_work = () => factory.was_not_told_to(x => x.create());
+
+        it should_return_an_empty_unit_of_work = () =>
+                                                     {
+                                                         result.should_not_be_null();
+                                                         result.should_be_an_instance_of<EmptyUnitOfWork>();
+                                                     };
+
+        static IUnitOfWork result;
+    }
+}
\ No newline at end of file
product/MoMoney.Service/Infrastructure/Transactions/UnitOfWorkSpecs.cs
@@ -0,0 +1,67 @@
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Infrastructure.Transactions;
+using Gorilla.Commons.Testing;
+
+namespace MoMoney.Service.Infrastructure.Transactions
+{
+    public class UnitOfWorkSpecs
+    {
+    }
+
+    [Concern(typeof (UnitOfWork))]
+    public abstract class behaves_like_unit_of_work : concerns_for<IUnitOfWork, UnitOfWork>
+    {
+        context c = () =>
+                        {
+                            session_context = the_dependency<IContext>();
+                            session = the_dependency<ISession>();
+                            key = the_dependency<IKey<ISession>>();
+                        };
+
+        static protected IContext session_context;
+        static protected ISession session;
+        static protected IKey<ISession> key;
+    }
+
+    [Concern(typeof (UnitOfWork))]
+    public abstract class when_a_unit_of_work_has_unsaved_changes : behaves_like_unit_of_work
+    {
+        context c = () => when_the(session).is_told_to(x => x.is_dirty()).it_will_return(true);
+    }
+
+    [Concern(typeof (UnitOfWork))]
+    public abstract class when_a_unit_of_work_has_no_changes : behaves_like_unit_of_work
+    {
+        context c = () => when_the(session).is_told_to(x => x.is_dirty()).it_will_return(false);
+    }
+
+    [Concern(typeof (UnitOfWork))]
+    public class when_checking_if_a_unit_of_work_has_any_unsaved_changes : when_a_unit_of_work_has_unsaved_changes
+    {
+        it should_return_true = () => result.should_be_true();
+        because b = () => { result = sut.is_dirty(); };
+        static bool result;
+    }
+
+    [Concern(typeof (UnitOfWork))]
+    public class when_commiting_a_unit_of_work : when_a_unit_of_work_has_unsaved_changes
+    {
+        it should_flush_the_current_session = () => session.was_told_to(x => x.flush());
+        because b = () => sut.commit();
+    }
+
+    [Concern(typeof (UnitOfWork))]
+    public class when_attempting_to_commit_a_unit_of_work : when_a_unit_of_work_has_no_changes
+    {
+        it should_not_flush_the_session = () => session.was_not_told_to(x => x.flush());
+        because b = () => sut.commit();
+    }
+
+    [Concern(typeof (UnitOfWork))]
+    public class when_disposing_of_a_unit_of_work : behaves_like_unit_of_work
+    {
+        it should_dispose_the_session = () => session.was_told_to(x => x.Dispose());
+        it should_remove_the_session_from_the_current_context = () => session_context.was_told_to(x => x.remove(key));
+        because b = () => sut.Dispose();
+    }
+}
\ No newline at end of file
product/MoMoney.Service/MoMoney.Service.csproj
@@ -98,6 +98,11 @@
     <Compile Include="Infrastructure\Security\IsInRole.cs" />
     <Compile Include="Infrastructure\Security\IsInRoleSpecs.cs" />
     <Compile Include="Infrastructure\Security\Role.cs" />
+    <Compile Include="Infrastructure\Transactions\EmptyUnitOfWork.cs" />
+    <Compile Include="Infrastructure\Transactions\UnitOfWork.cs" />
+    <Compile Include="Infrastructure\Transactions\UnitOfWorkFactory.cs" />
+    <Compile Include="Infrastructure\Transactions\UnitOfWorkFactorySpecs.cs" />
+    <Compile Include="Infrastructure\Transactions\UnitOfWorkSpecs.cs" />
     <Compile Include="Infrastructure\Updating\CancelUpdate.cs" />
     <Compile Include="Infrastructure\Updating\CancelUpdateSpecs.cs" />
     <Compile Include="Infrastructure\Updating\CurrentDeployment.cs" />
@@ -120,9 +125,6 @@
       <Name>MoMoney.DTO</Name>
     </ProjectReference>
   </ItemGroup>
-  <ItemGroup>
-    <Content Include="Application\Public\AddNewIncomeCommand.svc" />
-  </ItemGroup>
   <ItemGroup>
     <Folder Include="Domain\" />
     <Folder Include="Infrastructure\Core\" />
product/MyMoney/boot/container/registration/proxy_configuration/ServiceLayerConfiguration.cs
@@ -1,7 +1,6 @@
 using System.Security.Principal;
 using Gorilla.Commons.Infrastructure;
 using Gorilla.Commons.Infrastructure.Castle.DynamicProxy;
-using Gorilla.Commons.Infrastructure.Castle.DynamicProxy.Interceptors;
 using Gorilla.Commons.Utility.Core;
 using Gorilla.Commons.Utility.Extensions;
 using MoMoney.Service.Infrastructure.Security;
product/MyMoney/boot/container/registration/proxy_configuration/UnitOfWorkInterceptor.cs
@@ -0,0 +1,33 @@
+using Castle.Core.Interceptor;
+using Gorilla.Commons.Infrastructure.Eventing;
+using Gorilla.Commons.Utility.Core;
+using MoMoney.Service.Infrastructure.Transactions;
+
+namespace MoMoney.boot.container.registration.proxy_configuration
+{
+    public interface IUnitOfWorkInterceptor : IInterceptor
+    {
+    }
+
+    public class UnitOfWorkInterceptor : IUnitOfWorkInterceptor
+    {
+        readonly IEventAggregator broker;
+        readonly IUnitOfWorkFactory factory;
+
+        public UnitOfWorkInterceptor(IEventAggregator broker, IUnitOfWorkFactory factory)
+        {
+            this.broker = broker;
+            this.factory = factory;
+        }
+
+        public void Intercept(IInvocation invocation)
+        {
+            using (var unit_of_work = factory.create())
+            {
+                invocation.Proceed();
+                broker.publish<ICallback<IUnitOfWork>>(x => x.run(unit_of_work));
+                unit_of_work.commit();
+            }
+        }
+    }
+}
\ No newline at end of file
product/MyMoney/boot/container/registration/auto_wire_components_in_to_the.cs
@@ -4,7 +4,6 @@ using Gorilla.Commons.Infrastructure;
 using Gorilla.Commons.Infrastructure.Castle.Windsor.Configuration;
 using Gorilla.Commons.Infrastructure.Reflection;
 using Gorilla.Commons.Utility.Extensions;
-using MoMoney.Infrastructure.Container.Windsor.configuration;
 
 namespace MoMoney.boot.container.registration
 {
product/MyMoney/boot/container/registration/wire_up_the_data_access_components_into_the.cs
@@ -1,5 +1,4 @@
 using Gorilla.Commons.Infrastructure;
-using Gorilla.Commons.Infrastructure.Castle.DynamicProxy.Interceptors;
 using Gorilla.Commons.Infrastructure.Cloning;
 using Gorilla.Commons.Infrastructure.Container;
 using Gorilla.Commons.Infrastructure.Transactions;
@@ -8,6 +7,8 @@ using Gorilla.Commons.Utility.Extensions;
 using MoMoney.boot.container.registration.proxy_configuration;
 using MoMoney.DataAccess;
 using MoMoney.DataAccess.Db40;
+using MoMoney.DataAccess.Transactions;
+using MoMoney.Service.Infrastructure.Transactions;
 
 namespace MoMoney.boot.container.registration
 {
product/MyMoney/boot/container/registration/wire_up_the_infrastructure_in_to_the.cs
@@ -1,3 +1,4 @@
+using System.Collections;
 using System.ComponentModel;
 using System.Deployment.Application;
 using Gorilla.Commons.Infrastructure;
@@ -30,7 +31,7 @@ namespace MoMoney.boot.container.registration
             registry.transient(typeof (ITrackerEntryMapper<>), typeof (TrackerEntryMapper<>));
             registry.transient(typeof (IKey<>), typeof (TypedKey<>));
             registry.transient(typeof (IComponentFactory<>), typeof (ComponentFactory<>));
-            registry.singleton<IContext, Context>();
+            registry.singleton<IContext>( ()=>new Context(new Hashtable()));
 
             registry.singleton(() => AsyncOperationManager.SynchronizationContext);
             registry.singleton<AsyncOperation>(() => AsyncOperationManager.CreateOperation(new object()));
product/MyMoney/boot/container/registration/wire_up_the_presentation_modules.cs
@@ -5,7 +5,7 @@ using Gorilla.Commons.Infrastructure.Reflection;
 using Gorilla.Commons.Utility.Core;
 using Gorilla.Commons.Utility.Extensions;
 using MoMoney.boot.container.registration.proxy_configuration;
-using MoMoney.Modules.Core;
+using MoMoney.Presentation;
 using MoMoney.Presentation.Core;
 using MoMoney.Presentation.Model.Menu.File;
 using MoMoney.Presentation.Model.Menu.Help;
product/MyMoney/boot/container/ComponentExclusionSpecification.cs
@@ -2,7 +2,7 @@ using System;
 using Gorilla.Commons.Infrastructure.Castle.Windsor.Configuration;
 using Gorilla.Commons.Utility.Extensions;
 
-namespace MoMoney.Infrastructure.Container.Windsor.configuration
+namespace MoMoney.boot.container
 {
     public class ComponentExclusionSpecification : IComponentExclusionSpecification
     {
product/MyMoney/boot/container/ComponentExclusionSpecificationSpecs.cs
@@ -6,8 +6,8 @@ using developwithpassion.bdd.contexts;
 using Gorilla.Commons.Infrastructure.Castle.Windsor.Configuration;
 using Gorilla.Commons.Infrastructure.Container;
 using Gorilla.Commons.Testing;
+using Gorilla.Commons.Utility.Core;
 using MoMoney.Domain.Core;
-using MoMoney.Infrastructure.Container.Windsor.configuration;
 
 namespace MoMoney.boot.container
 {
@@ -85,7 +85,7 @@ namespace MoMoney.boot.container
 
     public class FakeEntity : IEntity
     {
-        public Guid id
+        public Id<Guid> id
         {
             get { throw new NotImplementedException(); }
         }
product/MyMoney/boot/container/type_extensions.cs
@@ -4,7 +4,7 @@ using Gorilla.Commons.Infrastructure.Container;
 using Gorilla.Commons.Utility.Core;
 using MoMoney.Domain.Core;
 
-namespace MoMoney.Infrastructure.Container.Windsor.configuration
+namespace MoMoney.boot.container
 {
     public static class type_extensions
     {
product/MyMoney/Modules/Core/LoadPresentationModulesCommand.cs
@@ -1,6 +1,7 @@
 using Gorilla.Commons.Infrastructure.Threading;
 using Gorilla.Commons.Utility.Core;
 using Gorilla.Commons.Utility.Extensions;
+using MoMoney.Presentation;
 
 namespace MoMoney.Modules.Core
 {
product/MyMoney/Modules/Core/LoadPresentationModulesCommandSpecs.cs
@@ -2,6 +2,7 @@ using developwithpassion.bdd.contexts;
 using Gorilla.Commons.Infrastructure.Threading;
 using Gorilla.Commons.Testing;
 using Gorilla.Commons.Utility.Core;
+using MoMoney.Presentation;
 
 namespace MoMoney.Modules.Core
 {
product/MyMoney/Modules/ApplicationMenuModule.cs
@@ -1,5 +1,5 @@
 using Gorilla.Commons.Infrastructure.Eventing;
-using MoMoney.Modules.Core;
+using MoMoney.Presentation;
 using MoMoney.Presentation.Model.Menu;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Presenters.Commands;
product/MyMoney/Modules/ApplicationShellModule.cs
@@ -1,4 +1,4 @@
-using MoMoney.Modules.Core;
+using MoMoney.Presentation;
 using MoMoney.Presentation.Presenters.Commands;
 using MoMoney.Presentation.Presenters.Shell;
 
product/MyMoney/Modules/DatabaseModule.cs
@@ -1,6 +1,6 @@
 using Gorilla.Commons.Infrastructure.Eventing;
 using MoMoney.DataAccess;
-using MoMoney.Modules.Core;
+using MoMoney.Presentation;
 
 namespace MoMoney.Modules
 {
product/MyMoney/Modules/GettingStartedModule.cs
@@ -1,5 +1,5 @@
 using Gorilla.Commons.Infrastructure.Eventing;
-using MoMoney.Modules.Core;
+using MoMoney.Presentation;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Presenters.Commands;
 using MoMoney.Presentation.Presenters.Shell;
product/MyMoney/Modules/MainMenuModule.cs
@@ -1,5 +1,5 @@
 using Gorilla.Commons.Infrastructure.Eventing;
-using MoMoney.Modules.Core;
+using MoMoney.Presentation;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Presenters.Commands;
 using MoMoney.Presentation.Presenters.Navigation;
product/MyMoney/Modules/NavigationModule.cs
@@ -1,5 +1,5 @@
 using Gorilla.Commons.Infrastructure.Eventing;
-using MoMoney.Modules.Core;
+using MoMoney.Presentation;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Presenters.Commands;
 using MoMoney.Presentation.Presenters.Navigation;
product/MyMoney/Modules/ToolbarModule.cs
@@ -1,5 +1,5 @@
 using Gorilla.Commons.Infrastructure.Eventing;
-using MoMoney.Modules.Core;
+using MoMoney.Presentation;
 using MoMoney.Presentation.Model.Menu;
 using MoMoney.Presentation.Model.messages;
 using MoMoney.Presentation.Presenters.Commands;
product/MyMoney/MyMoney.csproj
@@ -151,6 +151,7 @@
     <Compile Include="boot\container\registration\proxy_configuration\SecuringProxySpecs.cs" />
     <Compile Include="boot\container\registration\proxy_configuration\ServiceLayerConfiguration.cs" />
     <Compile Include="boot\container\registration\proxy_configuration\SynchronizedConfiguration.cs" />
+    <Compile Include="boot\container\registration\proxy_configuration\UnitOfWorkInterceptor.cs" />
     <Compile Include="boot\container\registration\wire_up_the_infrastructure_in_to_the.cs" />
     <Compile Include="boot\container\tear_down_the_container.cs" />
     <Compile Include="boot\container\registration\wire_up_the_data_access_components_into_the.cs" />