Commit 38421b7

unknown <mkhan@.arcresources.ca>
2009-10-22 22:58:51
cleaning up.
1 parent 2f3dd3f
product/database/db4o/Spiking/db40_spike_specs.cs
@@ -0,0 +1,91 @@
+using System.Collections.Generic;
+using System.IO;
+using Db4objects.Db4o;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.db4o.Spiking
+{
+    [Concern(typeof (Db4oFactory))]
+    public class when_opening_an_existing_database_ : concerns
+    {
+        before_each_observation be = () => {};
+
+        context c = () =>
+        {
+            original = new TestObject(88, "mo");
+            the_database_file = Path.GetTempFileName();
+            var configuration = Db4oFactory.NewConfiguration();
+            configuration.LockDatabaseFile(false);
+            database = Db4oFactory.OpenFile(configuration, the_database_file);
+        };
+
+        because b = () =>
+        {
+            database.Store(original);
+            database.Close();
+            database.Dispose();
+            database = Db4oFactory.OpenFile(the_database_file);
+            results = database.Query<ITestObject>().databind();
+        };
+
+        it should_be_able_to_load_the_original_contents = () => results.should_contain(original);
+
+        it they_should_be_equal = () => new TestObject(99, "gretzky").Equals(new TestObject(99, "gretzky"));
+
+        it should_only_contain_the_original_item = () => results.Count.should_be_equal_to(1);
+
+        after_each_observation ae = () =>
+        {
+            database.Close();
+            database.Dispose();
+        };
+
+        static ITestObject original;
+        static string the_database_file;
+        static IList<ITestObject> results;
+        static IObjectContainer database;
+    }
+
+    public interface ITestObject
+    {
+        int Id { get; }
+        string Name { get; }
+    }
+
+    public class TestObject : ITestObject
+    {
+        public TestObject(int id, string name)
+        {
+            Id = id;
+            Name = name;
+        }
+
+        public int Id { get; private set; }
+        public string Name { get; private set; }
+
+        public bool Equals(TestObject obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            if (ReferenceEquals(this, obj)) return true;
+            return obj.Id == Id && Equals(obj.Name, Name);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj)) return false;
+            if (ReferenceEquals(this, obj)) return true;
+            if (obj.GetType() != typeof (TestObject)) return false;
+            return Equals((TestObject) obj);
+        }
+
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                return (Id*397) ^ (Name != null ? Name.GetHashCode() : 0);
+            }
+        }
+    }
+}
\ No newline at end of file
product/database/db4o/ConfigureDatabaseStep.cs
@@ -0,0 +1,18 @@
+using Db4objects.Db4o.Config;
+using gorilla.commons.utility;
+
+namespace momoney.database.db4o
+{
+    public interface IConfigureDatabaseStep : Configuration<IConfiguration> {}
+
+    public class ConfigureDatabaseStep : IConfigureDatabaseStep
+    {
+        public void configure(IConfiguration item)
+        {
+            item.LockDatabaseFile(false);
+            //item.UpdateDepth(10);
+            //item.WeakReferences(true);
+            //item.AutomaticShutDown(true);
+        }
+    }
+}
\ No newline at end of file
product/database/db4o/ConfigureObjectContainerStep.cs
@@ -0,0 +1,20 @@
+using Db4objects.Db4o;
+using Db4objects.Db4o.Events;
+using Gorilla.Commons.Infrastructure.Logging;
+using gorilla.commons.utility;
+
+namespace momoney.database.db4o
+{
+    public interface IConfigureObjectContainerStep : Configuration<IObjectContainer> {}
+
+    public class ConfigureObjectContainerStep : IConfigureObjectContainerStep
+    {
+        public void configure(IObjectContainer item)
+        {
+            var registry = EventRegistryFactory.ForObjectContainer(item);
+            registry.ClassRegistered += (sender, args) => this.log().debug("class registered: {0}", args.ClassMetadata());
+            registry.Instantiated += (sender, args) => this.log().debug("class instantiated: {0}", args.Object.GetType().Name);
+            registry.Committed += (sender, args) => this.log().debug("added: {0}, updated: {1}, deleted: {2}", args.Added, args.Updated, args.Deleted);
+        }
+    }
+}
\ No newline at end of file
product/database/db4o/ConnectionFactory.cs
@@ -0,0 +1,34 @@
+using Db4objects.Db4o;
+using Db4objects.Db4o.Config;
+using Gorilla.Commons.Infrastructure.FileSystem;
+using gorilla.commons.utility;
+using momoney.database.transactions;
+
+namespace momoney.database.db4o
+{
+    public class ConnectionFactory : IConnectionFactory
+    {
+        readonly IConfigureDatabaseStep setup;
+        readonly IConfigureObjectContainerStep setup_container;
+
+        public ConnectionFactory(IConfigureDatabaseStep setup, IConfigureObjectContainerStep setup_container)
+        {
+            this.setup = setup;
+            this.setup_container = setup_container;
+        }
+
+        public IDatabaseConnection open_connection_to(File the_path_to_the_database_file)
+        {
+            var configuration = Db4oFactory.NewConfiguration();
+            setup.configure(configuration);
+            return new DatabaseConnection(get_container(the_path_to_the_database_file, configuration));
+        }
+
+        IObjectContainer get_container(File the_path_to_the_database_file, IConfiguration configuration)
+        {
+            return Db4oFactory
+                .OpenFile(configuration, the_path_to_the_database_file.path)
+                .and_configure_with(setup_container);
+        }
+    }
+}
\ No newline at end of file
product/database/db4o/DatabaseConnection.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using Db4objects.Db4o;
+using momoney.database.transactions;
+
+namespace momoney.database.db4o
+{
+    public class DatabaseConnection : IDatabaseConnection
+    {
+        readonly IObjectContainer container;
+
+        public DatabaseConnection(IObjectContainer container)
+        {
+            this.container = container;
+        }
+
+        public void Dispose()
+        {
+            container.Close();
+            container.Dispose();
+        }
+
+        public IEnumerable<T> query<T>()
+        {
+            return container.Query<T>();
+        }
+
+        public IEnumerable<T> query<T>(Predicate<T> predicate)
+        {
+            return container.Query(predicate);
+        }
+
+        public void delete<T>(T entity)
+        {
+            container.Delete(entity);
+        }
+
+        public void commit()
+        {
+            container.Commit();
+        }
+
+        public void store<T>(T entity)
+        {
+            container.Store(entity);
+        }
+    }
+}
\ No newline at end of file
product/database/repositories/AccountHolderRepository.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using momoney.database.transactions;
+using MoMoney.Domain.accounting;
+using MoMoney.Domain.repositories;
+
+namespace momoney.database.repositories
+{
+    public class AccountHolderRepository : IAccountHolderRepository
+    {
+        readonly ISession session;
+
+        public AccountHolderRepository(ISession session)
+        {
+            this.session = session;
+        }
+
+        public IEnumerable<IAccountHolder> all()
+        {
+            return session.all<IAccountHolder>();
+        }
+
+        public void save(IAccountHolder account_holder)
+        {
+            session.save(account_holder);
+        }
+    }
+}
\ No newline at end of file
product/database/repositories/BillRepository.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using momoney.database.transactions;
+using MoMoney.Domain.Accounting;
+using MoMoney.Domain.repositories;
+
+namespace momoney.database.repositories
+{
+    public class BillRepository : IBillRepository
+    {
+        readonly ISession session;
+
+        public BillRepository(ISession session)
+        {
+            this.session = session;
+        }
+
+        public IEnumerable<IBill> all()
+        {
+            return session.all<IBill>();
+        }
+    }
+}
\ No newline at end of file
product/database/repositories/CompanyRepository.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.commons.utility;
+using momoney.database.transactions;
+using MoMoney.Domain.Accounting;
+using MoMoney.Domain.repositories;
+
+namespace momoney.database.repositories
+{
+    public class CompanyRepository : ICompanyRepository
+    {
+        readonly ISession session;
+
+        public CompanyRepository(ISession session)
+        {
+            this.session = session;
+        }
+
+        public IEnumerable<ICompany> all()
+        {
+            return session.all<ICompany>();
+        }
+
+        public ICompany find_company_named(string name)
+        {
+            return session
+                .all<ICompany>()
+                .SingleOrDefault(x => x.name.is_equal_to_ignoring_case(name));
+        }
+
+        public ICompany find_company_by(Guid id)
+        {
+            return session.all<ICompany>().SingleOrDefault(x => x.id.Equals(id));
+        }
+
+        public void save(ICompany company)
+        {
+            session.save(company);
+        }
+    }
+}
\ No newline at end of file
product/database/repositories/IncomeRepository.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+using momoney.database.transactions;
+using MoMoney.Domain.Accounting;
+using MoMoney.Domain.repositories;
+
+namespace momoney.database.repositories
+{
+    public class IncomeRepository : IIncomeRepository
+    {
+        readonly ISession session;
+
+        public IncomeRepository(ISession session)
+        {
+            this.session = session;
+        }
+
+        public IEnumerable<IIncome> all()
+        {
+            return session.all<IIncome>();
+        }
+    }
+}
\ No newline at end of file
product/database/transactions/ChangeTracker.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public class ChangeTracker<T> : IChangeTracker<T> where T : Identifiable<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/database/transactions/ChangeTrackerFactory.cs
@@ -0,0 +1,23 @@
+using System;
+using Gorilla.Commons.Infrastructure.Container;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public class ChangeTrackerFactory : IChangeTrackerFactory
+    {
+        readonly IStatementRegistry statement_registry;
+        readonly DependencyRegistry registry;
+
+        public ChangeTrackerFactory(IStatementRegistry statement_registry, DependencyRegistry registry)
+        {
+            this.statement_registry = statement_registry;
+            this.registry = registry;
+        }
+
+        public IChangeTracker<T> create_for<T>() where T : Identifiable<Guid>
+        {
+            return new ChangeTracker<T>(registry.get_a<ITrackerEntryMapper<T>>(), statement_registry);
+        }
+    }
+}
\ No newline at end of file
product/database/transactions/ChangeTrackerFactorySpecs.cs
@@ -0,0 +1,22 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.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<Identifiable<Guid>>();
+        };
+
+        static IChangeTracker<Identifiable<Guid>> result;
+    }
+}
\ No newline at end of file
product/database/transactions/ChangeTrackerSpecs.cs
@@ -0,0 +1,103 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public class ChangeTrackerSpecs
+    {
+
+        [Concern(typeof (ChangeTracker<Identifiable<Guid>>))]
+        public abstract class behaves_like_change_tracker :
+            concerns_for<IChangeTracker<Identifiable<Guid>>, ChangeTracker<Identifiable<Guid>>>
+        {
+            context c = () =>
+            {
+                mapper = the_dependency<ITrackerEntryMapper<Identifiable<Guid>>>();
+                registry = the_dependency<IStatementRegistry>();
+            };
+
+            static protected ITrackerEntryMapper<Identifiable<Guid>> mapper;
+            static protected IStatementRegistry registry;
+        }
+
+        [Concern(typeof (ChangeTracker<Identifiable<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<Identifiable<Guid>>();
+                statement = an<IStatement>();
+                database = an<IDatabase>();
+                var entry = an<ITrackerEntry<Identifiable<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 Identifiable<Guid> item;
+            static IDatabase database;
+            static IStatement statement;
+        }
+
+        [Concern(typeof (ChangeTracker<Identifiable<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<Identifiable<Guid>>();
+                var registration = an<ITrackerEntry<Identifiable<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 Identifiable<Guid> item;
+        }
+
+        [Concern(typeof (ChangeTracker<Identifiable<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<Identifiable<Guid>>();
+                var entry = an<ITrackerEntry<Identifiable<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 Identifiable<Guid> item;
+        }
+    }
+}
\ No newline at end of file
product/database/transactions/Context.cs
@@ -0,0 +1,34 @@
+using System.Collections;
+
+namespace momoney.database.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/database/transactions/ContextFactory.cs
@@ -0,0 +1,15 @@
+namespace momoney.database.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/database/transactions/ContextFactorySpecs.cs
@@ -0,0 +1,31 @@
+using System.Collections;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace momoney.database.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/database/transactions/CurrentThread.cs
@@ -0,0 +1,19 @@
+using System.Threading;
+
+namespace momoney.database.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/database/transactions/IChangeTracker.cs
@@ -0,0 +1,17 @@
+using System;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public interface IChangeTracker : IDisposable
+    {
+        bool is_dirty();
+        void commit_to(IDatabase database);
+    }
+
+    public interface IChangeTracker<T> : IChangeTracker where T : Identifiable<Guid>
+    {
+        void register(T value);
+        void delete(T entity);
+    }
+}
\ No newline at end of file
product/database/transactions/IChangeTrackerFactory.cs
@@ -0,0 +1,10 @@
+using System;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public interface IChangeTrackerFactory
+    {
+        IChangeTracker<T> create_for<T>() where T : Identifiable<Guid>;
+    }
+}
\ No newline at end of file
product/database/transactions/IContext.cs
@@ -0,0 +1,10 @@
+namespace momoney.database.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/database/transactions/IDatabase.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public interface IDatabase
+    {
+        IEnumerable<T> fetch_all<T>() where T : Identifiable<Guid>;
+        void apply(IStatement statement);
+    }
+}
\ No newline at end of file
product/database/transactions/IDatabaseConnection.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+
+namespace momoney.database.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/database/transactions/IdentityMapProxy.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public class IdentityMapProxy<Key, Value> : IIdentityMap<Key, Value> where Value : Identifiable<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/database/transactions/IdentityMapSpecs.cs
@@ -0,0 +1,95 @@
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace momoney.database.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/database/transactions/IIdentityMap.cs
@@ -0,0 +1,59 @@
+using System.Collections.Generic;
+
+namespace momoney.database.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/database/transactions/IKey.cs
@@ -0,0 +1,12 @@
+using System.Collections;
+
+namespace momoney.database.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/database/transactions/IScopedStorage.cs
@@ -0,0 +1,9 @@
+using System.Collections;
+
+namespace momoney.database.transactions
+{
+    public interface IScopedStorage
+    {
+        IDictionary provide_storage();
+    }
+}
\ No newline at end of file
product/database/transactions/IStatement.cs
@@ -0,0 +1,7 @@
+namespace momoney.database.transactions
+{
+    public interface IStatement
+    {
+        void prepare(IDatabaseConnection connection);
+    }
+}
\ No newline at end of file
product/database/transactions/IStatementRegistry.cs
@@ -0,0 +1,11 @@
+using System;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public interface IStatementRegistry
+    {
+        IStatement prepare_delete_statement_for<T>(T entity) where T : Identifiable<Guid>;
+        IStatement prepare_command_for<T>(T entity) where T : Identifiable<Guid>;
+    }
+}
\ No newline at end of file
product/database/transactions/IThread.cs
@@ -0,0 +1,7 @@
+namespace momoney.database.transactions
+{
+    public interface IThread
+    {
+        T provide_slot_for<T>() where T : class, new();
+    }
+}
\ No newline at end of file
product/database/transactions/PerThread.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace momoney.database.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/database/transactions/PerThreadScopedStorage.cs
@@ -0,0 +1,19 @@
+using System.Collections;
+
+namespace momoney.database.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/database/transactions/PerThreadScopedStorageSpecs.cs
@@ -0,0 +1,34 @@
+using System.Collections;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace momoney.database.transactions
+{
+    public class PerThreadScopedStorageSpecs
+    {
+        [Concern(typeof (PerThreadScopedStorage))]
+        public class when_retrieving_the_storage_for_a_specific_thread :
+            concerns_for<IScopedStorage, PerThreadScopedStorage>
+        {
+            context c = () =>
+            {
+                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 Hashtable storage;
+        }
+    }
+}
\ No newline at end of file
product/database/transactions/Session.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Gorilla.Commons.Infrastructure.Logging;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public interface ISession : IDisposable
+    {
+        T find<T>(Guid guid) where T : Identifiable<Guid>;
+        IEnumerable<T> all<T>() where T : Identifiable<Guid>;
+        void save<T>(T entity) where T : Identifiable<Guid>;
+        void delete<T>(T entity) where T : Identifiable<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 : Identifiable<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 : Identifiable<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 : Identifiable<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 : Identifiable<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 : Identifiable<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 : Identifiable<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/database/transactions/SessionFactory.cs
@@ -0,0 +1,23 @@
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public interface ISessionFactory : Factory<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/database/transactions/SessionFactorySpecs.cs
@@ -0,0 +1,21 @@
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+
+namespace momoney.database.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/database/transactions/SessionNotStartedException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace momoney.database.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/database/transactions/SessionProvider.cs
@@ -0,0 +1,25 @@
+namespace momoney.database.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/database/transactions/SessionSpecs.cs
@@ -0,0 +1,181 @@
+using System;
+using System.Collections.Generic;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.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 Identifiable<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 : Identifiable<Guid> {}
+    }
+}
\ No newline at end of file
product/database/transactions/SingletonScopedStorage.cs
@@ -0,0 +1,14 @@
+using System.Collections;
+
+namespace momoney.database.transactions
+{
+    public class SingletonScopedStorage : IScopedStorage
+    {
+        static readonly IDictionary storage = new Hashtable();
+
+        public IDictionary provide_storage()
+        {
+            return storage;
+        }
+    }
+}
\ No newline at end of file
product/database/transactions/StatementRegistry.cs
@@ -0,0 +1,48 @@
+using System;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public class StatementRegistry : IStatementRegistry
+    {
+        public IStatement prepare_delete_statement_for<T>(T entity) where T : Identifiable<Guid>
+        {
+            return new DeletionStatement<T>(entity);
+        }
+
+        public IStatement prepare_command_for<T>(T entity) where T : Identifiable<Guid>
+        {
+            return new SaveOrUpdateStatement<T>(entity);
+        }
+    }
+
+    public class SaveOrUpdateStatement<T> : IStatement where T : Identifiable<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/database/transactions/TrackerEntry.cs
@@ -0,0 +1,47 @@
+using System.Reflection;
+using Gorilla.Commons.Infrastructure.Logging;
+
+namespace momoney.database.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/database/transactions/TrackerEntryMapper.cs
@@ -0,0 +1,27 @@
+using Gorilla.Commons.Infrastructure.Cloning;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public interface ITrackerEntryMapper<T> : Mapper<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/database/transactions/TrackerEntrySpecs.cs
@@ -0,0 +1,145 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.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 : Identifiable<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/database/transactions/Transaction.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using gorilla.commons.utility;
+
+namespace momoney.database.transactions
+{
+    public interface ITransaction
+    {
+        IIdentityMap<Guid, T> create_for<T>() where T : Identifiable<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 : Identifiable<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 : Identifiable<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/database/transactions/TransactionSpecs.cs
@@ -0,0 +1,113 @@
+using System;
+using developwithpassion.bdd.contexts;
+using Gorilla.Commons.Testing;
+using gorilla.commons.utility;
+
+namespace momoney.database.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<Identifiable<Guid>>(); };
+
+        static IIdentityMap<Guid, Identifiable<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 : Identifiable<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/database/transactions/TypedKey.cs
@@ -0,0 +1,50 @@
+using System.Collections;
+
+namespace momoney.database.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/database/database.csproj
@@ -0,0 +1,152 @@
+๏ปฟ<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>9.0.30729</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{580E68A8-EDEE-4350-8BBE-A053645B0F83}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>momoney.database</RootNamespace>
+    <AssemblyName>momoney.database</AssemblyName>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="bdddoc, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\build\lib\test\bdd.doc\bdddoc.dll</HintPath>
+    </Reference>
+    <Reference Include="Db4objects.Db4o, Version=7.5.57.11498, Culture=neutral, PublicKeyToken=6199cd4f203aa8eb, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\build\lib\app\db40\Db4objects.Db4o.dll</HintPath>
+    </Reference>
+    <Reference Include="Db4objects.Db4o.Linq, Version=7.5.57.11498, Culture=neutral, PublicKeyToken=6199cd4f203aa8eb, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\build\lib\app\db40\Db4objects.Db4o.Linq.dll</HintPath>
+    </Reference>
+    <Reference Include="developwithpassion.bdd, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\build\lib\test\developwithpassion\developwithpassion.bdd.dll</HintPath>
+    </Reference>
+    <Reference Include="gorilla.commons.infrastructure, Version=2009.5.5.1633, Culture=neutral, PublicKeyToken=687787ccb6c36c9f, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\build\lib\app\gorilla\gorilla.commons.infrastructure.dll</HintPath>
+    </Reference>
+    <Reference Include="gorilla.commons.utility, Version=2009.5.5.1633, Culture=neutral, PublicKeyToken=687787ccb6c36c9f, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\build\lib\app\gorilla\gorilla.commons.utility.dll</HintPath>
+    </Reference>
+    <Reference Include="gorilla.testing, Version=2009.5.5.194, Culture=neutral, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\build\lib\test\gorilla\gorilla.testing.dll</HintPath>
+    </Reference>
+    <Reference Include="MbUnit.Framework, Version=2.4.2.175, Culture=neutral, PublicKeyToken=5e72ecd30bc408d5">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\build\lib\test\mbunit\MbUnit.Framework.dll</HintPath>
+    </Reference>
+    <Reference Include="Rhino.Mocks, Version=3.5.0.1337, Culture=neutral, PublicKeyToken=0b3305902db7183f, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\..\build\lib\test\rhino.mocks\Rhino.Mocks.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Xml.Linq">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Data.DataSetExtensions">
+      <RequiredTargetFramework>3.5</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="db4o\ConfigureObjectContainerStep.cs" />
+    <Compile Include="db4o\Spiking\db40_spike_specs.cs" />
+    <Compile Include="repositories\AccountHolderRepository.cs" />
+    <Compile Include="repositories\BillRepository.cs" />
+    <Compile Include="repositories\CompanyRepository.cs" />
+    <Compile Include="repositories\IncomeRepository.cs" />
+    <Compile Include="db4o\ConfigureDatabaseStep.cs" />
+    <Compile Include="db4o\ConnectionFactory.cs" />
+    <Compile Include="ObjectDatabase.cs" />
+    <Compile Include="db4o\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="..\Domain\domain.csproj">
+      <Project>{BE790BCC-4412-473F-9D0A-5AA48FE7A74F}</Project>
+      <Name>domain</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Properties\" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file
product/database/IConnectionFactory.cs
@@ -0,0 +1,10 @@
+using Gorilla.Commons.Infrastructure.FileSystem;
+using momoney.database.transactions;
+
+namespace momoney.database
+{
+    public interface IConnectionFactory
+    {
+        IDatabaseConnection open_connection_to(File the_path_to_the_database_file);
+    }
+}
\ No newline at end of file
product/database/IDatabaseConfiguration.cs
@@ -0,0 +1,11 @@
+using Gorilla.Commons.Infrastructure.FileSystem;
+
+namespace momoney.database
+{
+    public interface IDatabaseConfiguration
+    {
+        void open(File file);
+        void copy_to(string path);
+        void close(string name);
+    }
+}
\ No newline at end of file
product/database/ObjectDatabase.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Gorilla.Commons.Infrastructure.FileSystem;
+using gorilla.commons.utility;
+using momoney.database.transactions;
+using File = Gorilla.Commons.Infrastructure.FileSystem.File;
+
+namespace momoney.database
+{
+    public class ObjectDatabase : IDatabase, IDatabaseConfiguration
+    {
+        readonly IConnectionFactory factory;
+        File path;
+
+        public ObjectDatabase(IConnectionFactory factory)
+        {
+            this.factory = factory;
+            path = new ApplicationFile(Path.GetTempFileName());
+        }
+
+        public IEnumerable<T> fetch_all<T>() where T : Identifiable<Guid>
+        {
+            using (var connection = factory.open_connection_to(path_to_database()))
+            {
+                return connection.query<T>().ToList();
+            }
+        }
+
+        public void apply(IStatement statement)
+        {
+            using (var connection = factory.open_connection_to(path_to_database()))
+            {
+                statement.prepare(connection);
+                connection.commit();
+            }
+        }
+
+        public void open(File file)
+        {
+            path = new ApplicationFile(Path.GetTempFileName());
+            file.copy_to(path.path);
+        }
+
+        public void copy_to(string new_path)
+        {
+            path.copy_to(new_path);
+        }
+
+        public void close(string name)
+        {
+            path.delete();
+            path = new ApplicationFile(Path.GetTempFileName());
+        }
+
+        File path_to_database()
+        {
+            return path;
+        }
+    }
+}
\ No newline at end of file
product/Presentation/Model/eventing/ClosingProjectEvent.cs
@@ -0,0 +1,8 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+    public class ClosingProjectEvent : IEvent
+    {
+    }
+}
\ No newline at end of file
product/Presentation/Model/eventing/ClosingTheApplication.cs
@@ -0,0 +1,8 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+    public class ClosingTheApplication : IEvent
+    {
+    }
+}
\ No newline at end of file
product/Presentation/Model/eventing/FinishedRunningCommand.cs
@@ -0,0 +1,14 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+    public class FinishedRunningCommand : IEvent
+    {
+        public FinishedRunningCommand(object running_command)
+        {
+            completed_action = running_command;
+        }
+
+        public object completed_action { get; private set; }
+    }
+}
\ No newline at end of file
product/Presentation/Model/eventing/NewProjectOpened.cs
@@ -0,0 +1,14 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+    public class NewProjectOpened : IEvent
+    {
+        public NewProjectOpened(string path)
+        {
+            this.path = path;
+        }
+
+        public string path { private set; get; }
+    }
+}
\ No newline at end of file
product/Presentation/Model/eventing/SavedChangesEvent.cs
@@ -0,0 +1,8 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+    public class SavedChangesEvent : IEvent
+    {
+    }
+}
\ No newline at end of file
product/Presentation/Model/eventing/StartedRunningCommand.cs
@@ -0,0 +1,14 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+    public class StartedRunningCommand : IEvent
+    {
+        public StartedRunningCommand(object running_command)
+        {
+            running_action = running_command;
+        }
+
+        public object running_action { get; private set; }
+    }
+}
\ No newline at end of file
product/Presentation/Model/eventing/UnhandledErrorOccurred.cs
@@ -0,0 +1,15 @@
+using System;
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+    public class UnhandledErrorOccurred : IEvent
+    {
+        public UnhandledErrorOccurred(Exception error)
+        {
+            this.error = error;
+        }
+
+        public Exception error { get; private set; }
+    }
+}
\ No newline at end of file
product/Presentation/Model/eventing/UnsavedChangesEvent.cs
@@ -0,0 +1,8 @@
+using MoMoney.Service.Infrastructure.Eventing;
+
+namespace momoney.presentation.model.events
+{
+    public class UnsavedChangesEvent : IEvent
+    {
+    }
+}
\ No newline at end of file
product/Presentation/Model/Menu/Help/DisplayInformationAboutTheApplication.cs
@@ -0,0 +1,23 @@
+using gorilla.commons.utility;
+using momoney.presentation.presenters;
+using MoMoney.Presentation.Presenters;
+
+namespace MoMoney.Presentation.model.menu.help
+{
+    public interface IDisplayInformationAboutTheApplication : Command {}
+
+    public class DisplayInformationAboutTheApplication : IDisplayInformationAboutTheApplication
+    {
+        public DisplayInformationAboutTheApplication(IRunPresenterCommand run_presenter)
+        {
+            this.run_presenter = run_presenter;
+        }
+
+        public void run()
+        {
+            run_presenter.run<IAboutApplicationPresenter>();
+        }
+
+        readonly IRunPresenterCommand run_presenter;
+    }
+}
\ No newline at end of file
product/Presentation/Presenters/DisplayTheSplashScreen.cs
@@ -0,0 +1,34 @@
+using System;
+using MoMoney.Presentation.Presenters;
+using momoney.presentation.views;
+using MoMoney.Service.Infrastructure.Threading;
+
+namespace momoney.presentation.presenters
+{
+    public class DisplayTheSplashScreen : ISplashScreenState
+    {
+        readonly ITimer timer;
+        readonly ISplashScreenView view;
+        readonly ISplashScreenPresenter presenter;
+
+        public DisplayTheSplashScreen(ITimer timer, ISplashScreenView view, ISplashScreenPresenter presenter)
+        {
+            this.timer = timer;
+            this.view = view;
+            this.presenter = presenter;
+            timer.start_notifying(presenter, new TimeSpan(50));
+        }
+
+        public void update()
+        {
+            if (view.current_opacity() < 1)
+            {
+                view.increment_the_opacity();
+            }
+            else
+            {
+                timer.stop_notifying(presenter);
+            }
+        }
+    }
+}
\ No newline at end of file