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.