Logo

NHibernate

The object-relational mapper for .NET

A fluent interface to NHibernate - Part 3 - Mapping Relations

Blog Signature Gabriel

This is the third post in a series of articles where I want to analyze and describe the new upcoming mapping interface providing a fluent interface to NHibernate for the mapping of a domain model to the underlying database.

You can get the source code of the solution accompanying this post here.

In the time between my last post and today a lot has happened to the mapping framework. The contributors are busily improving the source and are also very responsive to my questions and remarks.

In this post I want to focus on how one can map various relations between entities and value objects of a domain model.

Scenario 3

Domain Model

Let's have a look at the following simplified model


I have a Blog which has an author of type Person. Each Blog can have many Posts. To each Post readers can give feedback in the form of Comments. Comments are considered value objects in this model, that is they have no identity and are immutable (a reader cannot edit its comment after it has been published...). All other elements are true entities. If I consider the Blog to be the root object then the relation between Blog and Person is of type many-to-one (a person can be the owner of more than one blog). On the other hand the relation between Blog and Post is of type one-to-many. The parent and the children are both entities.

A special case (as we will see) is the relation between Post and Comment (since Comment is a value object). It is also of type one-to-many but this time the parent is an entity and the children are value objects.

Mapping

How can we map this? Well, let's start with the easy one. In this simplified model the Person class has no external dependencies and is thus easy to map

public class PersonMap : ClassMap<Person>
{
    public PersonMap()
    {
        Id(x => x.Id);
        Map(x => x.FirstName);
        Map(x => x.LastName);
    }
}

The Comment class has also no external dependencies. I want to treat the Comment as a value object. So I have to map it as follows

public class PostMap : ClassMap<Post>
{
    public PostMap()
    {
        Id(x => x.Id);
        Map(x => x.Title);
        Map(x => x.Body);
        Map(x => x.PublicationDate);
        HasMany<Comment>(x => x.Comments)
            .Component(c =>
                           {
                               c.Map(x => x.Text);
                               c.Map(x => x.AuthorEmail);
                               c.Map(x => x.CreationDate);
                           }).AsSet();
    }
}

Note that I have to tell the framework that I want to have a set by using the AsSet method. The default is a bag (represented by a list in .Net).

Finally we can map the Blog class which is now easy

public class BlogMap : ClassMap<Blog>
{
    public BlogMap()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        References(x => x.Author);
        HasMany<Post>(x => x.Posts).AsSet().Cascade.AllDeleteOrphan();
    }
}

Note that we map the many-to-one relation between Blog and Person with the aid of the References method. Note further that we map the collection of Posts with the HasMany method. Since by default this method maps to a "bag" we have to further specify that we want to map with a "set" (--> see my post on collection mapping for the various types of collections). Finally I also tell the framework that I want to have all posts of a blog deleted, if the blog is deleted and that I want to cascade all updates and inserts.

The XML generated by the above mapping class is shown below. Now you can ask yourself which way of mapping your entities you prefer...

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false" 
                   assembly="FluentMapping.Domain" 
                   namespace="FluentMapping.Domain.Scenario3">
  <class name="Post" table="[Post]" xmlns="urn:nhibernate-mapping-2.2">
    <id name="Id" column="Id" type="Int64">
      <generator class="identity" />
    </id>
    <property name="PublicationDate" column="PublicationDate" type="DateTime">
      <column name="PublicationDate" />
    </property>
    <property name="Body" column="Body" length="100" type="String">
      <column name="Body" />
    </property>
    <property name="Title" column="Title" length="100" type="String">
      <column name="Title" />
    </property>
    <set name="Comments" cascade="none">
      <key column="Post_id" />
      <composite-element class="FluentMapping.Domain.Scenario3.Comment, 
                                FluentMapping.Domain, Version=1.0.0.0, 
                                Culture=neutral, PublicKeyToken=null">
        <property name="CreationDate" column="CreationDate" type="DateTime">
          <column name="CreationDate" />
        </property>
        <property name="AuthorEmail" column="AuthorEmail" length="100" type="String">
          <column name="AuthorEmail" />
        </property>
        <property name="Text" column="Text" length="100" type="String">
          <column name="Text" />
        </property>
      </composite-element>
    </set>
  </class>
</hibernate-mapping>

 

Ok I admit, when hand crafting the XML we can skip some of the elements, but still...

Testing

All tests use the base class FixtureBase which I have introduced in my first post about the mapping framework. For completeness I present here the source once again

public class FixtureBase
{
    protected SessionSource SessionSource { get; set; }
    protected ISession Session { get; private set; }
 
    [SetUp]
    public void SetupContext()
    {
        Before_each_test();
    }
 
    [TearDown]
    public void TearDownContext()
    {
        After_each_test();
    }
 
    protected virtual void Before_each_test()
    {
        SessionSource = new SessionSource(new TestModel());
        Session = SessionSource.CreateSession();
        SessionSource.BuildSchema(Session);
        CreateInitialData(Session);
        Session.Flush();
        Session.Clear();
    }
 
    protected virtual void After_each_test()
    {
        Session.Close();
        Session.Dispose();
    }
 
    protected virtual void CreateInitialData(ISession session)
    {
    }
}

Now let's test whether we can create a blog and add posts to it. Let's start with the former. I define a base class for all my further blog related tests as

public class Blog_Fixture : FixtureBase
{
    protected Person author;
 
    protected override void CreateInitialData(ISession session)
    {
        base.CreateInitialData(session);
        author = new Person {FirstName = "Gabriel", LastName = "Schenker"};
        session.Save(author);
    }
}

In the CreateInitialData method I create an author object since every blog has to have an author. I save this author object to the database. To make the author available to all child test classes I have declared it as a protected filed. Now to the test which tries to create a new blog and verifies that it has be written correctly and completely to the database

[TestFixture]
public class When_no_blog_exists : Blog_Fixture
{
    [Test]
    public void Can_add_new_blog()
    {
        var blog = new Blog {Name = "Gabriel's Blog", Author = author};
        Session.Save(blog);
        Session.Flush();
        Session.Clear();
 
        var fromDb = Session.Get<Blog>(blog.Id);
 
        fromDb.ShouldNotBeNull();
        fromDb.ShouldNotBeTheSameAs(blog);
        fromDb.Id.ShouldEqual(blog.Id);
        fromDb.Name.ShouldEqual(blog.Name);
        fromDb.Author.ShouldNotBeNull();
        fromDb.Author.Id.ShouldEqual(blog.Author.Id);
    }
}

Note that I have inherited this test class from the previously implemented Blog_Fixture class. In the test method I first create a new blog instance. Then I save it to the database. I then flush and clear the session instance to guarantee that all the object(s) in NHibernate's session cache are written to the DB and that the cache is cleared afterwards such as that when a read operation is invoked the respective object is really retrieved from the database.

If you wonder where all these ShouldXXX methods in the second part of the test come from then wonder no longer. These are extension methods which I have implemented. They make all the asserts that you normally would do with the aid on NUnit's Assert class. But like this the code is way more readable, isn't it? If you wonder how these methods are implemented then please have a look into the source code of the solution accompanying this post. Search for the class SpecificationExtensions.

When running this test it succeeds!

But we have seen in the past that the framework offers us some help to reduce the size of our test methods. So let's revisit the test and leverage the framework.

[Test]
public void Can_add_new_blog_revisited()
{
    new PersistenceSpecification<Blog>(Session)
        .CheckProperty(x => x.Name, "Gabriel's Blog")
        .CheckProperty(x => x.Author, author)
        .VerifyTheMappings();
}

Yeah, much shorter! That's what I call wrist friendly... Of course when run also this test succeeds.

Second we want to try to add a post to an already existing blog. I have the following code for that

 
[TestFixture]
public class When_a_blog_exists : Blog_Fixture
{
    private Blog blog;
 
    protected override void CreateInitialData(ISession session)
    {
        base.CreateInitialData(session);
        blog = new Blog {Name = "Gabriel's Blog", Author = author};
        session.Save(blog);
    }
 
    [Test]
    public void Can_add_post_to_blog()
    {
        var post = new Post
                    {
                        Title = "First Post",
                        Body = "Just a test",
                        PublicationDate = DateTime.Today
                    };
        blog.Posts.Add(post);
        Session.Update(blog);
 
        Session.Flush();
        Session.Clear();
 
        var fromDb = Session.Get<Blog>(blog.Id);
 
        fromDb.Posts.Count.ShouldEqual(1);
        fromDb.Posts.First().Id.ShouldEqual(post.Id);
    }
}

In the CreateInitialData I setup my context which in this case is: I have a blog in the database. In the test method I take this existing blog instance and add a new post to it. I then tell the session to update the blog. As usual I flush and clear the session before I assert that the operation was indeed successful.

Now I reload the blog from the database and test whether it has one post as expected and whether it's the post we have added to the blog (it suffices to test the post's id). Note that the method First() applied to the Posts collection of the blog (on the last line of the test) is also an extension method. This extension method just returns the first element of any collection of objects implementing IEnumerable<T> onto which it is applied.

Again when we run the test it is successful.

We want to leverage the framework once more and thus I revisit the test

[Test]
public void Can_add_post_to_blog_revisited()
{
    List<Post> posts = new List<Post>();
    posts.AddRange(new[]
                    {
                        new Post {
                                    Title = "First Post",
                                    Body = "Just a test",
                                    PublicationDate = DateTime.Today
                                 },
                        new Post {
                                    Title = "Second Post",
                                    Body = "Just another test",
                                    PublicationDate = DateTime.Today.AddDays(-1)
                                 },
                    });
 
    new PersistenceSpecification<Blog>(Session)
        .CheckProperty(x => x.Name, "Gabriel's Blog")
        .CheckProperty(x => x.Author, author)
        .CheckList(x => x.Posts, posts)
        .VerifyTheMappings();
}

Once again I use our friend the PersistenceSpecification class. This time I use it's method CheckList to test the Posts collection of the blog instance. This method expects a collection of Post objects which I have defined in the first part of this test. Here I have defined two posts in the list but also a single one would suffice for the test.

Let me resume: to completely test the mapping of the Blog class I need four lines of code! Nice.

The last thing we have left to test is whether we can add comments to our posts. First I setup my context; that is I have a blog with one post. I also prepare a comment which I can then later on add to the post.

[TestFixture]
public class When_a_blog_with_a_post_exists : Blog_Fixture
{
    private Blog blog;
    private Post post;
    private Comment comment;
 
    protected override void CreateInitialData(ISession session)
    {
        base.CreateInitialData(session);
        blog = new Blog { Name = "Gabriel's Blog", Author = author };
        post = new Post
                   {
                       Title = "First Post",
                       Body = "Just a test",
                       PublicationDate = DateTime.Today
                   };
        blog.Posts.Add(post);
        session.Save(blog);
 
        comment = new Comment("This is my comment", DateTime.Today, "someone@gmail.com");
    }
}

Once my context is set up writing the test is easy. I read the post from the database, add the prepared comment to it and then flush and clear the session (note that the session automatically realizes that the post is dirty and that an update must be made to the database). The I re-read the post from the database and verify that indeed one comment was added and that it is the comment which I expect (by comparing it's Id).

[Test]
public void Can_add_comment_to_post()
{
    var thePost = Session.Get<Post>(post.Id);
    thePost.Comments.Add(comment);
    
    Session.Flush();
    Session.Clear();
 
    var fromDb = Session.Get<Post>(post.Id);
    fromDb.Comments.Count.ShouldEqual(1);
    fromDb.Comments.First().Equals(comment);
}

And again the test succeeds. The test using the PersistenceSpecification class is like this

[Test]
public void Can_add_comment_to_post_revisited()
{
    new PersistenceSpecification<Post>(Session)
        .CheckProperty(x => x.Title, "Some title")
        .CheckProperty(x => x.Body, "Some text")
        .CheckProperty(x => x.PublicationDate, DateTime.Today)
        .CheckComponentList(x => x.Comments, new[] { comment })
        .VerifyTheMappings();
}

which succeeds as usual!

Source Code

You can get the source code of the solution accompanying this post here.

Summary

In this post I have shown you that the mapping framework is indeed ready for mapping more advanced scenarios. I have shown you how to map one-to-many relations where either the children are entities or the children are value objects. I also have shown how to map many-to-one relations.

Enjoy

Blog Signature Gabriel


Posted Sat, 06 September 2008 07:39:00 AM by gabriel.schenker
Filed under: mapping, collections, value object, relation

comments powered by Disqus
© NHibernate Community 2024