Series Navigation

  1. Setup
  2. Mapping and Entity Equality

Entity Equality

We've got some more house keeping to do on our Entity object before proceding.  Because entities are comporable based upon Id, a standard use of the == operator will produce undesirable results.  When using an ORM, this is particularly important.  You will often have instances of objects, especially objects in child relationships, that are the same as, but not reference equal to, one another.

Test Cases for Desired Entity Equality Behavior

I'm a believer in documentation via test cases, so here are my test cases that demonstrate the desired behavior:

    [TestFixture]

    public class EntityEqualityContext

    {

        [Test]

        public void TwoTransientEntitiesShouldNotBeEqual()

        {

            var entity1 = new TestEntity();

            var entity2 = new TestEntity();

 

            Assert.AreNotEqual(entity1, entity2, "Different transient entities should not be equal.");

        }

 

        [Test]

        public void TwoReferencesToSameTransientEntityShouldBeEqual()

        {

            var entity1 = new TestEntity();

            var entity2 = entity1;

 

            Assert.AreEqual(entity1, entity2, "Two references to the same transient entity should be equal.");

        }

 

        [Test]

        public void EntitiesWithSameIdShouldBeEqual()

        {

            var entity1 = new TestEntity();

            var entity2 = new TestEntity();

 

            entity1.SetId(1);

            entity2.SetId(1);

 

            Assert.AreEqual(entity1, entity2, "Entities with same id should be equal.");

        }

 

        [Test]

        public void EntitiesWithDifferentIdShouldNotBeEqual()

        {

            var entity1 = new TestEntity();

            var entity2 = new TestEntity();

 

            entity1.SetId(1);

            entity2.SetId(2);

 

            Assert.AreNotEqual(entity1, entity2, "Entities with different ids should not be equal.");

        }

 

        [Test]

        public void EntityShouldNotEqualTransientEntity()

        {

            var entity1 = new TestEntity();

            entity1.SetId(1);

 

            var entity2 = new TestEntity();

 

            Assert.AreNotEqual(entity1, entity2, "Entity and transient entity should not be equal.");

        }

 

        [Test]

        public void EntitiesWithSameIdButDifferentTypesShouldNotBeEqual()

        {

            var entity1 = new TestEntity();

            var entity2 = new OtherEntity();

 

            entity1.SetId(1);

            entity2.SetId(1);

 

            Assert.AreNotEqual(entity1, entity2, "Entities of different types should not be equal even if they have the same id.");

        }

 

        /// <summary>

        /// A test entity class created so that we can excercise

        /// the functionality of the entity equaltiy members.

        /// </summary>

        public class TestEntity : Entity

        {

            private int id;

 

            public override int Id

            {

                get { return id; }

            }

 

            public void SetId(int id)

            {

                this.id = id;

            }

        }

 

        public class OtherEntity : Entity

        {

            private int id;

 

            public override int Id

            {

                get { return id; }

            }

 

            public void SetId(int id)

            {

                this.id = id;

            }

        }

    }

 

Summarized, these tests say that entities with id of 0 should only be equal if they are the same object.  This is reference equality.  Entities with an id should be equal if their ids are equal and they are of the same type.  This is identity equality.

Entity with Equaltiy Members

    public abstract class Entity: IEquatable<Entity>

    {

        private int id;

        private int transientHashCode;

 

        public virtual int Id { get { return id; } }

 

        public override bool Equals(object obj)

        {

            return Equals(obj as Entity);

        }

 

        public virtual bool Equals(Entity obj)

        {

            if (obj == null) return false;

 

            if (IsTransient)

            {

                return ReferenceEquals(this, obj);

            }

 

            return obj.Id == Id && obj.GetType() == GetType();

        }

 

        public override int GetHashCode()

        {

            if (IsTransient)

            {

                if (transientHashCode == 0)

                {

                    transientHashCode = base.GetHashCode();

                }

                return transientHashCode;

            }

            return id;

        }

 

        private bool IsTransient

        {

            get { return Id == 0; }

        }

    }

 

For more information about why insuring that your entity equality is in order, please see The NHibernate FAQ.

As this is a blog example, we will need a class to represent posts.

The Post Class

    public class Post : Entity, IEquatable<Post>

    {

        public virtual string Title { get; set; }

 

        public virtual string Body { get; set; }

 

        /// <summary>

        /// Indicates whether the current object is equal to another object of the same type.

        /// </summary>

        /// <returns>

        /// true if the current object is equal to the <paramref name="other" /> parameter; otherwise, false.

        /// </returns>

        /// <param name="other">

        /// An object to compare with this object.

        /// </param>

        public virtual bool Equals(Post other)

        {

            return base.Equals(other);

        }

    }

 

In the future, we will add other properties like a collection of comments.  For now, we will keep it simple in order to demonstrate the fluent mapping.

What's the Deal with the Virtual Members

NHibernate makes use of proxies for advanced features like lazy loading.  The default proxy factory uses Castle's DynamicProxy.  DyanmicProxy generates inheritance based proxies; therefore, it can only work if members are marked as virtual so that it can override them.  There are other proxy choices; however, I will not be covering them.  Making members virtual is a small price to pay for the conveniance in my opinion.

Mapping the Post Entity to the Database

Generating the mappings for your entities could not be easier with fluent nhibernates automapper.

Here is a simple utitliy class to add to the Service project:

    public class DomainMapper

    {

        private readonly IPersistenceConfigurer persistenceConfigurer;

 

        public DomainMapper(IPersistenceConfigurer persistenceConfigurer)

        {

            this.persistenceConfigurer = persistenceConfigurer;

        }

 

        public void Configure(Configuration configuration)

        {

            Fluently.Configure(configuration).Mappings(mapping => mapping.AutoMappings.Add(GetMapper())).Database(

                persistenceConfigurer).BuildSessionFactory();

        }

 

        protected virtual AutoPersistenceModel GetMapper()

        {

            var persistenceModel = new AutoPersistenceModel(typeof (Entity).Assembly)

                .Where(type => !type.IsAbstract && typeof (Entity).IsAssignableFrom(type))

                .WithConvention(convention =>

                                    {

                                        convention.IdConvention =

                                            id => id.Access.AsReadOnlyPropertyThroughCamelCaseField().GeneratedBy.HiLo(100.ToString());

                                        convention.IsBaseType = type => type == typeof (Entity);

                                        convention.DefaultStringLength = 255;

                                    });

            return persistenceModel;

        }

    }

 

I like to use HiLo for the id convention and avoid using Native Identity.  For more information about the identity generators available using NHibernate, please see Vadi's Rants.

The DefaultStringLength is just a personal preference.  The default is 100 if you do not specify a convention.

The IsBaseType is necesarry so fluent nhibernate does not create joined table entities from everything that inherits from Entity.

In the Blog.Service.Test project, add this utility class:

    public class TestDomainMapper: DomainMapper

    {

        public TestDomainMapper() : base(SQLiteConfiguration.Standard.InMemory().ShowSql())

        {

        }

    }

 

This class simply inerits from DomainMapper and provides a SQLite in memory database as the persistence configuration.  Using an in memory database for our tests will make them run much faster.  As the project grows, it is advisable to also run your integration tests vs the actual database that you will be using; however, running them against an in memory database during standard development saves time and is very conveniant.

In my first post, I attempted to make a thorough list of the tools that would be used in the project.  I neglected at least one though: SQLite.  I may have neglected more, so do not be surprised if we have to add more binaries throughout the series to our libraries directory.  System.Data.SQLite can be downloaded here.  Add it to trunk/libraries/sqlite and reference it from the Blog.Service.Test project.

Fluent NHibernate provides a useful helper for integration tests: PersistenceSpecification<T>.  Here is what the initial Post integration test looks like:

[TestFixture]

public class MappingIntegrationContext

{

    protected ISession Session { get; private set; }

 

    [SetUp]

    public void BeforeEachTest()

    {

        var configuration = new Configuration();

        new TestDomainMapper().Configure(configuration);

 

        var sessionFactory = configuration.BuildSessionFactory();

        Session = sessionFactory.OpenSession();

 

        IDbConnection connection = Session.Connection;

 

        string[] scripts = configuration.GenerateSchemaCreationScript(Dialect.GetDialect(configuration.Properties));

        scripts.ToList().ForEach(script =>

                                     {

                                         var cmd = connection.CreateCommand();

                                         cmd.CommandText = script;

                                         cmd.ExecuteNonQuery();

                                     });

 

        Session.Flush();

    }

 

    [Test]

    public void PostIntegrationTest()

    {

        new PersistenceSpecification<Post>(Session)

            .CheckProperty(x => x.Title, "A Test Post")

            .CheckProperty(x => x.Body, "This is where the content of my post will be.  Yay.")

            .VerifyTheMappings();

    }

}

 

This is a good start, but obviously as our domain model grows and we need more test classes, this SetUp can be abstracted into its own base class that other test classes can inherit from. 

public abstract class FixtureBase

{

    [SetUp]

    public void BeforeEachTest()

    {

        var configuration = new Configuration();

        new TestDomainMapper().Configure(configuration);

 

        var sessionFactory = configuration.BuildSessionFactory();

        Session = sessionFactory.OpenSession();

 

        IDbConnection connection = Session.Connection;

 

        string[] scripts = configuration.GenerateSchemaCreationScript(Dialect.GetDialect(configuration.Properties));

        scripts.ToList().ForEach(script =>

                                     {

                                         var cmd = connection.CreateCommand();

                                         cmd.CommandText = script;

                                         cmd.ExecuteNonQuery();

                                     });

 

        Session.Flush();

    }

 

    protected ISession Session { get; private set; }

}

 

Now the noise of the SetUp method can be taken out of the MappingIntegrationContext and we have a reusable base class for future test classes.

Implementing IRepository

Next, we will implement our repository.  NHibernate.Burrow AppBlock makes this trivial.  Reference NHibernate.Burrow.AppBlock from the Blog.Service assembly.

using System.Collections.Generic;

using Blog.Domain;

using NHibernate.Burrow.AppBlock.DAOBases;

 

namespace Blog.Service

{

    public class Repository<T>: GenericDAO<T>, IRepository<T> where T : Entity

    {

        /// <summary>

        /// Find an entity by unique identifier.

        /// </summary>

        /// <param name="id">Unique identifier of the entity.</param>

        /// <returns>Entity if found, <c>null</c> otherwise.</returns>

        public T Find(int id)

        {

            return base.Get(id);

        }

 

        /// <summary>

        /// Find all entities.

        /// </summary>

        /// <returns>Sequence of entities found.</returns>

        public new IEnumerable<T> FindAll()

        {

            return base.FindAll();

        }

 

        /// <summary>

        /// Save (insert or update as appropriate) <paramref name="entity"/> on next commit of the unit of work.

        /// </summary>

        /// <param name="entity">Entity to be saved.</param>

        public new void Save(T entity)

        {

            base.Save(entity);

        }

 

        /// <summary>

        /// Delete <paramref name="entity"/> on next commit of the unit of work.

        /// </summary>

        /// <param name="entity">Entity to be deleted.</param>

        public new void Delete(T entity)

        {

            base.Delete(entity);

        }

    }

}

 

For the basic functionality, AppBlock provides everything that we need.

Update: Some of you want the sample project that I am working from, so I will add a zip of it soon.

In the next post, we will wire up the Inversion of Control in our MVC app and create a controller for Post CRUD.

kick it on DotNetKicks.com