NHibernate 3.2 Mapping Entities and Value Objects by Code

One of the major new features of NHibernate 3.2 is Mapping by Code, this is one of the features I waited for long time ago, I don’t like mapping by xml files, it is not strongly typed, error prone, and not programmer friendly.

Most of developers like me who prefer mapping by code used to use FluentNhibernate to map their classes by code.

In this post I will introduce the initial domain model of the sample application SellAndBuy and how it is mapped using the new mapping by code feature of NHibernate 3.2.

The Model:

The model consists of five main entities:

  • Customer: A very simple entity describing a customer, has no associations or any complex constructs.
  • Item: An item entity description to describe item to be advertised for, the item is associated to a category as a many to one association. The item can contain one or more pictures through the association to a set of PictureInfo value object through the base class PicturableBase.
  • ItemCategory: Category entity describes the category of the item, a category contains a set of available conditions as a one to many aggregation.
  • ItemCondition: A value object describing a condition (New, Good, Broken, …).
  • Ad: A relatively complex entity describing the ad transaction, it is associated to a customer as many to one association, associated to an item as many to one association, and associated to a condition value object describing the item’s condition. The ad can contain one or more pictures through the association to a set of PictureInfo value object through the base class PicturableBase.
Now to the mapping code:

To map any entity you need to create a class that inherits from ClassMapping<T> and define the mapping behavior in the constructor as method calls with lambda expression parameters.

Lazy() method sets the lazy loading behavior.

Id() method defines the key of the table.

Property() method defines a property mapping to database field.

ManyToOne() method defines association to an entity with many to one cardinality.

Bag() method defines a collection of objects.

Component() is used to map a grouping of fields that is not an entity, it is very suitable for mapping value objects as they don’t have identity and they are immutable.

The following are mappings of the sample domain model.

  • CustomerMap:
    public class CustomerMap : ClassMapping<Customer>
    {
        public CustomerMap()
        {
            Lazy(false);
            Id(x => x.ID, map => map.Generator(Generators.HighLow,
                          gmap => gmap.Params(new {max_low = 100})));
            Property(x => x.FirstName, map => map.NotNullable(true));
            Property(x => x.LastName, map => map.NotNullable(true));
            Property(x => x.Email, map =>
                                       {
                                           map.Unique(true);
                                           map.Length(50);
                                           map.NotNullable(true);
                                       });
        }
    }

This mapping code first mark the class as non-lazy loaded, define the int Id field with a generation strategy, defines three properties.

  • ItemMap:
    public class ItemMap : ClassMapping<Item>
    {
        public ItemMap()
        {
            Lazy(false);
            Id(x => x.ID, map => map.Generator(Generators.HighLow,
                          gmap => gmap.Params(new {max_low = 100})));
            Property(x => x.Name,
                          map => { map.Length(150); map.NotNullable(true); });
            Property(x => x.Description,
                          map => { map.Length(1000); map.NotNullable(true); });
            ManyToOne(x => x.Category, map =>
                                           {
                                               map.Column("CategoryID");
                                               map.NotNullable(true);
                                               map.Lazy(LazyRelation.NoProxy);
                                           });
            Bag(x => x.Pictures,
               collectionMapping =>
               {
                   collectionMapping.Table("ItemPictures");
                   collectionMapping.Access(Accessor.NoSetter);
                   collectionMapping.Cascade(Cascade.All);
                   collectionMapping.Key(k => k.Column("ItemID"));
                   collectionMapping.Lazy(CollectionLazy.NoLazy);
               },
               mapping => mapping.Component(PictureInfoMap.Mapping()));
        }
    }

This mapping code mark the class as non-lazy loaded, define the Id field with a generation strategy, defines name and description simple properties.

Then comes the association of many to one to category entity through Category property, and define the mapping column name in the database, and define the lazy loading strategy of the collection.

The association to a list of PictureInfo value objects is done through Bag given the property name Pictures, and a collection mapping which describes the database table to be mapped to, the cascading strategy, the key name, and lazy loading strategy, and the accessor strategy to state that this collection will have no setter.

As the PictureInfo is a value object and not an entity, there is no separate mapper class for it and it does not have identity, so the mapping would be in place through Component, but for modularity the behavior of component mapping for PictureInfo is encapsulated in PictureInfoMap.Mapping() method which is defined as the following:

    public class PictureInfoMap
    {
        private const int MaxImageLength = 3145728; // = 3 MB

        public static Action<IComponentElementMapper<PictureInfo>> Mapping()
        {
            return c =>
            {
                c.Property(p => p.Title, map => map.Length(150));
                c.Property(p => p.FileName, map => map.NotNullable(true));
                c.Property(p => p.IsMain, map => map.NotNullable(true));
                c.Property(p => p.Picture, map =>
                               {
                                    map.NotNullable(true);
                                    map.Length(MaxImageLength);
                               });
            };
        }
    }
  • ItemCategoryMap:
        public ItemCategoryMap()
        {
            Lazy(false);
            Id(x => x.ID, map => map.Generator(Generators.HighLow, gmap =>
                                 gmap.Params(new {max_low = 100})));
            Property(x => x.Name, map => { map.Length(150); map.NotNullable(true); });
            Bag(x => x.AvailableConditions,
                collectionMapping =>
                    {
                        collectionMapping.Access(Accessor.NoSetter);
                        collectionMapping.Cascade(Cascade.All);
                        collectionMapping.Key(k => k.Column("ItemConditionID"));
                        collectionMapping.Lazy(CollectionLazy.NoLazy);
                    },
                mapping => mapping.Component(ItemConditionMap.MappingElements()));
        }
    }

This mapping code mark the class as non-lazy loaded, define the Id field with a generation strategy, defines name property.

And again it defines a bag of component for ItemCondition value object.

  • AdMap:
    public class AdMap : ClassMapping<Ad>
    {
        public AdMap()
        {
            Lazy(false);
            Id(x => x.ID, map => map.Generator(Generators.HighLow, gmap =>
                                 gmap.Params(new {max_low = 100})));
            ManyToOne(x => x.Customer, map =>
                                           {
                                               map.Column("CustomerID");
                                               map.NotNullable(true);
                                               map.Lazy(LazyRelation.NoProxy);
                                           });
            ManyToOne(x => x.Item, map =>
            {
                map.Column("ItemID");
                map.NotNullable(true);
            });
            Property(x => x.Description, map => { map.Length(1000);
                                                  map.NotNullable(true); });
            Property(x => x.Price, map => map.NotNullable(true));
            Property(x => x.CreationDate, map => map.NotNullable(true));
            Component(x => x.Condition, ItemConditionMap.Mapping());
            Bag(x => x.Pictures,
                collectionMapping =>
                    {
                        collectionMapping.Table("AdPictures");
                        collectionMapping.Access(Accessor.NoSetter);
                        collectionMapping.Cascade(Cascade.All);
                        collectionMapping.Key(k => k.Column("AdID"));
                        collectionMapping.Lazy(CollectionLazy.NoLazy);
                    },
                mapping => mapping.Component(PictureInfoMap.Mapping()));
        }
    }

Two many to one associations to customer and item, one value object association to Condition as a component, and like Item a bag of PictureInfo value object.

About these ads

11 thoughts on “NHibernate 3.2 Mapping Entities and Value Objects by Code

  1. Thanks for great post, but I’ve 2 questions:

    1- FluentNhibernate is out there for long time and this feature looks like it, so What will this add to NHibernate? Is it just the same DLL instead of 2?
    Maybe this will make FluentNHibernate is side by side with NHiberante releases instead of supporting the new releases, and IMHO learning to write XML “.hbm.xml” files will give you the big picture of FlunetNHibernate and what is doing in the background.

    2-Can this code:

    Property(x => x.Name,
    map => { map.Length(150); map.NotNullable(true); });

    Coded like this in fluent way:

    Property(x => x.Name).Length(150).NotNullable(true);

    And again thanks for the post and I’m having so much fun comment on it ;)

    • Nice comment :)

      1- As you said, it is simpler and more reliable to rely on one source and one dll, as an official feature of NHibernate instead of adding a dependency upon another 3rd party vendor (Fluent Nhibernate). and you are right knowing to write and understand XML will give you more knowledge about the mapping as it is the original way of mapping, but once you mastered the API of mapping you will gain the confidence of mapping by code without thinking in terms of XML, experience users of Fluent Nhibernate will feel pretty much comfortable mapping by code without thinking of XML equivalent.

      2. Unfortunately the NHibernate’s API is not technically a Fluent Interface :(, that is why I didn’t call it fluent rather it is mapping by code API, the API needs to be enhanced to support fluent style as it would be easier and more flexible.

  2. Pingback: Nhibernate 3.2 component collection mapping | DIGG LINK

  3. Just found your article – this is really nicely written and explained, thank you very much. It’s just helped me to cleanly map a collection-of-enum without too much head scratching.

  4. Using bycode is certainly not the path of least resistance and won’t be until they can get at least some basic official documentation for it. Its to easy to run into some edge case and wind up spending ours googling for help. The closest thing to docs is this guys epic post series – http://stackoverflow.com/questions/5777898/docs-examples-for-nhibernate-3-2-mapping-by-code. However like most articles discussing bycode on the web it only skirts over mapping by convention. Trying to get an entirely by convention many to many mapping going here – http://stackoverflow.com/questions/13330554/nhibernate-bycode-mapping-how-to-map-manytomany-entirely-by-convention – been on it for a cpl of days :(

      • I can’t find any sql script. Only Script.PostDeployment.sql and Script.PreDeployment.sql with comments. Also same thing at the codeplex.com.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s