NHibernate Mapping – <map/>

time to read 14 min | 2607 words

I am not going to talk about all the options that NHibernate has for collections, I already did it for <set/>, and most of that are pretty similar. Instead, I am going to show off the unique features of NHibernate’s <map/>, and then some how crazy you can get with it.

Let us start with the simplest possible scenario, a key/value strings collection, which looks like this:

public class User
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual IDictionary<string, string> Attributes { get; set; }
}

And the mapping is pretty simple as well:

<class name="User" table="Users">
    <id name="Id">
        <generator class="hilo"/>
    </id>
    <property name="Name"/>

    <map name="Attributes" table="UserAttributesAreBoring">
        <key column="UserId"/>
        <index column="AttributeName" type="System.String"/>
        <element column="Attributevalue" type="System.String"/>
    </map>
</class>

How are we using this? Let us start by writing it to the database:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    id = session.Save(new User
    {
        Name = "a",
        Attributes = new Dictionary<string, string>
        {
            {"height", "1.9cm"},
            {"x","y"},
            {"is_boring","true, very much so"}
        },
    });
    tx.Commit();
}

Which give us:

image

And when we want to read it, we just use:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    var user = session.Get<User>(id);
    Console.WriteLine(user.Name);

    foreach (var kvp in user.Attributes)
    {
        Console.WriteLine("\t{0} - {1}", kvp.Key,
            kvp.Value);
    }

    tx.Commit();
}

And the SQL:

image

This simple mapping is quite boring, so let us try to do something a bit more interesting, let us map a complex value type:

public virtual IDictionary<string, Position> FavoritePlaces { get; set; }

// Position is a value type, defined as:
public class Position
{
    public decimal Lang { get; set; }
    public decimal Lat { get; set; }

    public override string ToString()
    {
        return string.Format("Lang: {0}, Lat: {1}", Lang, Lat);
    }
}

Which we can map using:

<map name="FavoritePlaces" table="UsersFavoritePlaces">
    <key column="UserId"/>
    <index column="PlaceName" type="System.String"/>
    <composite-element class="Position">
        <property name="Lang"/>
        <property name="Lat"/>
    </composite-element>
</map>

Using composite-element, we can map value types (types that have no identifiers, and only exists as part of their parent). We can use it using the following code:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    id = session.Save(new User
    {
        Name = "a",
        FavoritePlaces = new Dictionary<string, Position>
        {
            {"home", new Position{Lang = 10,Lat = 94.4m}},
            {"vacation house", new Position{Lang = 130,Lat = 194.4m}}
        },
    });
    tx.Commit();
}

And that give us:

image

By now you are probably already are familiar with what reading the FavoritePlaces collection would look like, so we won’t bother. Instead, let us look at a more complex example, what happen if we want the key of the map to be a complex value type as well? Let us look at this:

public virtual IDictionary<FavPlaceKey, Position> ComplexFavoritePlaces { get; set; }

// FavPlaceKey is another value type
public class FavPlaceKey
{
    public virtual string Name { get; set; }
    public virtual string Why { get; set; }

    public override string ToString()
    {
        return string.Format("Name: {0}, Why: {1}", Name, Why);
    }
}

We can map this collection as:

<map name="ComplexFavoritePlaces" table="UsersComplexFavoritePlaces" >
    <key column="UserId"/>
    <composite-index class="FavPlaceKey">
        <key-property  name="Name"/>
        <key-property name="Why"/>
    </composite-index>
    <composite-element class="Position">
        <property name="Lang"/>
        <property name="Lat"/>
    </composite-element>
</map>

And using this is pretty simple as well:

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    id = session.Save(new User
    {
        Name = "a",
        ComplexFavoritePlaces = new Dictionary<FavPlaceKey, Position>
        {
            {
                new FavPlaceKey
                {
                    Name = "home",
                    Why = "what do you mean, why?"
                },
                new Position
                {
                    Lang = 123,
                    Lat = 29.3m
                }
            }
        },
    });
    tx.Commit();
}

Which results in:

image

But that still isn’t over, I am happy to say. So far we have dealt only with value types, what about using entities? You can probably guess:

id = session.Save(new User
{
    Name = "a",
    CommentorsOnMyPosts = new Dictionary<User, Post>
    {
        {anotherUser, post1}
    }
});

With the mapping:

<map name="CommentorsOnMyPosts" table="UserCommentorsOnPosts">
    <key column="UserId"/>
    <index-many-to-many column="CommentorUserId" class="User"/>
    <many-to-many class="Post" column="PostId"/>
</map>

Giving us:

image

But how are we going to read it?

using (var session = sessionFactory.OpenSession())
using (var tx = session.BeginTransaction())
{
    var user = session.Get<User>(id);
    Console.WriteLine(user.Name);

    foreach (var kvp in user.CommentorsOnMyPosts)
    {
        Console.WriteLine("\t{0} - {1}", kvp.Key.Name,
            kvp.Value.Title);
    }

    tx.Commit();
}

The resulting SQL is quite interesting:

image

When we load the commentors, we join directly to the posts (value), but we have to use a different query to load the user. This is something that you should be aware of, when using a <map/> with entity key.

I would like to point out that I have still not covered all the options for <map/>, there are even more options (Although, IDictionary<K, IList<V >> is not something that is possible to map, for the problematic SQL statements that would be required to do so).

And, of course, you are free to mix all types as you would like, entity key and value type, or a string to an entity, or a complex value type to an entity.

Happy (n)hibernating… :-)

a