May 24 2009

Mapping a Twitter like domain with Fluent NHibernate

Category: Uncategorizedbengtbe @ 10:00

I’m currently learning NHibernate, and one of the best ways to learn is to blog about it :) Since I’m not a big fan of XML files, I also wanted to use Fluent NHibernate to do the mapping. As an example I will use a social messaging domain, similar to Twitter. I will also use a Top-down approach, starting with the domain model, writing the mapping, and finally creating the database schema using the SchemaExport class in NHibernate.

The domain model

Let’s start with a look at the domain model:

Quacker Domain

The domain model mainly consists of the User entity and the Message entity and their associations:

  • The user has a list of Messages that he/she has posted.
  • The user has a list of other users that are interested in his/hers messages, called Followers.
  • The user has a list of other users whose messages he/she is interested, called Following.
  • The message has a PostedBy reference to the user who posted it.

Both entities derive from the Entity<T> base class that contains the Id property and implements Equals and GetHashCode methods.

The domain classes

Let’s take a close look at the domain classes. Below is the class for the Message entity:

public class Message : Entity<Message>

{

    public virtual string Text { get; set; }

    public virtual User PostedBy { get; set; }

    public virtual DateTime CreatedAt { get; set; }

}

It contains the message text, a reference to the user who posted it, and the time it was created.

Below is the class for the User entity:

public class User : Entity<User>

{

    public User()

    {

        Name = Url = Email = “”;

        Followers = new List<User>();

        Following = new List<User>();

        Messages = new List<Message>();

    }

    public virtual string Name { get; set; }

    public virtual string Username { get; set; }

    public virtual string Email { get; set; }

    public virtual string Url { get; set; }

    public virtual IList<User> Followers { get; private set; }

    public virtual IList<User> Following { get; private set; }

    public virtual IList<Message> Messages { get; private set; }

    public virtual void AddFollowing(User user)

    {

        if (Following.Contains(user)) return;

        Following.Add(user);

        user.Followers.Add(this);

    }

    public virtual void AddMessage(string messageText)

    {

        var message = new Message

                          {

                              Text = messageText,

                              PostedBy = this,

                              CreatedAt = DateTime.Now

                          };

        Messages.Add(message);

    }

}

 

The User class contains some properties about the user, a list of followers, a list of following users, and a list of messages. It also contains two methods:

  • AddFollowing(User user) - Adds another user to the list of users that the current user is following. Since this is a bidirectional relationship, it also adds the current user to the other user’s followers.
  • AddMessage(string messageText) – Adds a new message that the user has posted. Sets the PostedBy to the current user.

Why is everything virtual?

When using NHibernate the domain model can be close to Persistence Ignorant (PI), which means that you don’t have to take any special considerations when designing the domain model, like derive from a specific base class. However to support transparent lazy-loading, NHibernate needs to return a proxy that inherit from your entity and override the properties.

If you dislike marking everything with virtual you can turn of lazy-loading, but I don’t recommend doing this.

Mapping with Fluent NHibernate

As mentioned, Fluent NHibernate lets you write mapping with strongly typed C# code, instead of the traditional NHibernate XML mapping files. This allows you to use tools like ReSharper to alter both the domain and mapping files when refactoring.

The mapping files must derive from ClassMap<T> and the mapping code is done inside the constructor using lambda expressions and a fluent syntax.

Let’s take a look at the mapping file for the Message entity:

public class MessageClassMap : ClassMap<Message>

{

    public MessageClassMap()

    {

        Id(m => m.Id).GeneratedBy.GuidComb();

        Map(m => m.Text).Not.Nullable().WithLengthOf(140);

        Map(m => m.CreatedAt).Not.Nullable();

        References(m => m.PostedBy, “PostedBy”).Not.Nullable();

    }

}

 

Explanation of the mapping:

  • The Id method maps the Id property as the identifier of the Message entity. It should be generated using the GuidComb algorithm.
  • The Map method maps the Text property. It also specifies that is should not be null, and that the max length is 120.
  • The Map method also maps the CreatedAt property.
  • The References method maps the PostedBy property as a many-to-one relationship between the message and user. Hence, one message belongs to a single user, but a user can have many messages. It also specifies that the column name in the database should be PostedBy.

Let’s take a look at the mapping file for the User entity:

public class UserClassMap : ClassMap<User>

{

    public UserClassMap()

    {

        Id(u => u.Id);

        Map(u => u.Name).Not.Nullable().WithLengthOf(50);

        Map(u => u.Username).Not.Nullable().WithLengthOf(50).Unique();

        Map(u => u.Email).Not.Nullable().WithLengthOf(100);

        Map(u => u.Url).Not.Nullable().WithLengthOf(100);

        HasMany(u => u.Messages).LazyLoad().Cascade.All()

            .KeyColumnNames.Add(“PostedBy”);

        HasManyToMany(u => u.Following).LazyLoad()

            .WithParentKeyColumn(“FollowerId”)

            .WithChildKeyColumn(“FollowingId”);

        HasManyToMany(u => u.Followers).LazyLoad()

            .WithParentKeyColumn(“FollowingId”)

            .WithChildKeyColumn(“FollowerId”).Inverse();

    }

}

 

Explanation of the mapping:

  • The mapping of the Id, Name, Email, Url does not contain any new syntax.
  • The mapping of the UserName property uses the Unique method to specify uniqueness. This will create a unique constrain in the database schema.
  • The HasMany method maps the Messages property as a one-to-many between the user entity and message entity. The Cascade.All means the message will become persistent when you add it to a persistent user. The messages will also be deleted when you delete the user. It also specifies that the name of the column should be PostedBy in order to make sure that it uses the same column as specified in the message mapping.
  • The HasManyToMany method maps the Following as a many-to-many mapping between users with specified column names.
  • The HasManyToMany method maps the Followers as a many-to-many mapping between users. This is the inverse of the Following association, hence the call to Inverse().

We also call LazyLoad() to specify that we want support for lazy loading of the lists.

Is something missing in the mapping?

You might have noticed that we often don’t specify the table or column name in the mapping. In these cases the table will get the same name as the class, and the column will get the same name as the property. Below is an example of how to specify table and column names:

WithTable(“TBL_MESSAGES”);

Map(c => c.CreatedAt, “COL_CREATEDAT”).Not.Nullable();

You also don’t need to specify the type of the properties (e.g. that PostedBy is of type User). NHibernate uses reflection to determine the type.

Creating the database

In a top-down approach you change the database after you make changes to the domain or the mapping. During development you can recreate the database using the SchemaExport class in a test or console application. Below is an example using an explicit test in NUnit:

    [TestFixture]

    public class DatabaseSetup

    {

        [Test, Explicit]

        public void SetupDatabase()

        {

            FluentConfiguration conf = NHConfiguration.CreateConfiguration(false);

            conf.ExposeConfiguration(BuildSchema)

                .BuildSessionFactory();

        }

        private static void BuildSchema(Configuration conf)

        {

            new SchemaExport(conf).Drop(false, true);

            new SchemaExport(conf).Create(false, true);

        }

    }

}

The test is marked with the Explicit attribute because you want to control when this test is run, e.g. you do not want to run it every time you run your tests. The SetupDatabase() test receives a FluentConfiguration from the NHibernate configuration class in the project. The database is created in the BuildSchema method that is passed to the ExposeConfiguration method. The call to BuildSessionFactory() is needed to trigger the FluentConfiguration to actually execute the call to the BuildSchema method.

Finally the database schema

Below is the database schema that has been created from the domain model and the mappings:

Quacker Database Schema

Some notes about the database schema:

  • The length of the nvarchar fields are according to the WithLengthOf call in the mappings.
  • All fields are not nullable because of the Not.Nullable() call in the mappings.
  • The many-to-many association between users are represented using the UserToUser junction table.
  • The one-to-many association between the message and users are done by the PostedBy column in the Message table.
  • It is not shown in the diagram, but Username has a unique constrain due to the Unique() call in the mappings.

Conclusion

In this post I have shown how to use Fluent NHibernate in a top-down approach in a Twitter like domain. Since I’m fairly new to NHibernate I welcome any comments and suggestions!

kick it on DotNetKicks.com Shout it

Tags: ,

4 Responses to “Mapping a Twitter like domain with Fluent NHibernate”

  1. Bart Czernicki says:

    This is the only thing really stopping me from using nHibernate in a large database OR/M. For a small project (<20 tables) is is not too bad. To me this is a lot of code to write to do PK/FK relationships.

    Some of the databases, I have worked with have over 700+ objects mapping this without a visual editor (LINQ to SQL or EF) kills maintainability for me. This is one of the reasons I really like Entity Framework 4.0.

  2. bengtbe says:

    This is the only thing really stopping me from using nHibernate in a large database OR/M. For a small project (<20 tables) is is not too bad. To me this is a lot of code to write to do PK/FK relationships.

    Some of the databases, I have worked with have over 700+ objects mapping this without a visual editor (LINQ to SQL or EF) kills maintainability for me. This is one of the reasons I really like Entity Framework 4.0.

    Hi Bart. Thanks for your comment :)

    Remember that I’m in this example using a top-down approach. The time I’m using to write the mapping is much less than it would take me to: open SQL Server Manager, create the tables and relationships, thereafter use a design tool (LINQ to SQL or EF) to create a domain model.

    Regarding maintainability, this top-down approach allows me to use an in-memory SQLite database to write tests against the domain-model. For me, this beats the maintainabilty of any design tool.

    When working bottom-up with an existing database with many tables, I agree that it is tiresome to write all this mapping. However there are some tools (e.g. MyGeneration and CodeSmith) you can use to generate both the domain classes and mapping files (unfortunately in XML) from the database.

    Also Fluent NHibernate has some Auto Mapping functionality where you don’t have to specify the mapping, it is based on conventions. Sounds interesting, but I haven’t looked closely at it.

    But I also must say that Entity Framework 4.0 looks promising. Seems like much is improved since the last version.

    BTW: Your Silverlight blog look interesting!

  3. Bart Czernicki says:

    nHibernate is great and proven, I just am not sold on it yet for very large projects where you have developers of different skills creating/maintaining the persistance layer.

    With nHibernate you definitely need to have higher-end skills in order to be effective…interfaces, OOP, lambda expressions, fluent methods etc. That was my main point about having a visual representation of the data (even to just look at it). For jr/mid developers who are not good with those skills…nHibernate is simply no the right tool for them.

  4. bengtbe says:

    nHibernate is great and proven, I just am not sold on it yet for very large projects where you have developers of different skills creating/maintaining the persistance layer.

    With nHibernate you definitely need to have higher-end skills in order to be effective…interfaces, OOP, lambda expressions, fluent methods etc. That was my main point about having a visual representation of the data (even to just look at it). For jr/mid developers who are not good with those skills…nHibernate is simply no the right tool for them.

    I agree that in order to user NHibernate correctly you will need at least one experienced developer who knows NHibernate to do the mapping, and set up a good architecture around NHibernate. I also agree that NHibernate is probably more difficult to learn than Linq to SQL, and probably also Entity Framework.

    But I also believe that an experienced developer should do the database design and mapping with Linq to SQL and Entity Framework, even though a junior developer probably can get something up and running.

    When it comes to the skills you mentioned I doesn’t agree. I would never start a large project without the use of interfaces and OOP, in order to get a good design and do TDD. NHibernate can be used without knowing lambda and fluent methods, these syntaxes are only required by Fluent NHibernate. However you can’t do much querying in Linq to SQL or Linq to Entities without lambda, fluent methods, and learning the Linq syntax.

    In a NHibernate solution the domain model is the data. If you need a visual representation of it you create a class diagram, like the on at the top of this post.

    In my opinion, Linq to SQL is suited for small projects with a simple database model and require minimal knowledge. NHibernate and Entity Framework can be used in most scenarios but require more knowledge in order to use them correctly.

    But of course you have to take into account the skill set of the team you are working with and choose a technology that is optimal. In an ideal world all developers would be experienced :)

Leave a Reply