Try fast search NHibernate

11 January 2010

Map NHibernate using your API

I would say you the story of this stuff but perhaps is matter for another post if you are interested in it. In this post I will show you an example how create your API to map your domain without use XML.

The Domain

DomainDiagram

Complex enough ?

I’m going to use various types of properties, relations as many-to-one, many-to-many, one-to-one, entities and components, collections of components, collections of scalar-types, collections as set, map, bag, list, hierarchy strategy as table-per-class and table-per-class-hierarchy… enough complete… well… at least for a blog post.

The API

For this example the API is a hbm-xml-mimic:

IMapper map = new Mapper();
map.Assembly(typeof (Animal).Assembly);
map.NameSpace(typeof (Animal).Namespace);

map.Class<Animal, long>(animal => animal.Id, id => id.Generator = Generators.Native, rc =>
{
rc.Property(animal => animal.Description);
rc.Property(animal => animal.BodyWeight);
rc.ManyToOne(animal => animal.Mother);
rc.ManyToOne(animal => animal.Father);
rc.ManyToOne(animal => animal.Zoo);
rc.Property(animal => animal.SerialNumber);
rc.Set(animal => animal.Offspring, cm => cm.OrderBy(an => an.Father), rel => rel.OneToMany());
});

map.JoinedSubclass<Reptile>(jsc => { jsc.Property(reptile => reptile.BodyTemperature); });

map.JoinedSubclass<Lizard>(jsc => { });

map.JoinedSubclass<Mammal>(jsc =>
{
jsc.Property(mammal => mammal.Pregnant);
jsc.Property(mammal => mammal.Birthdate);
});

map.JoinedSubclass<DomesticAnimal>(jsc => { jsc.ManyToOne(domesticAnimal => domesticAnimal.Owner); });

map.JoinedSubclass<Cat>(jsc => { });

map.JoinedSubclass<Dog>(jsc => { });

map.JoinedSubclass<Human>(jsc =>
{
jsc.Component(human => human.Name, comp =>
{
comp.Property(name => name.First);
comp.Property(name => name.Initial);
comp.Property(name => name.Last);
});
jsc.Property(human => human.NickName);
jsc.Property(human => human.Height);
jsc.Property(human => human.IntValue);
jsc.Property(human => human.FloatValue);
jsc.Property(human => human.BigDecimalValue);
jsc.Property(human => human.BigIntegerValue);
jsc.Bag(human => human.Friends, cm => { }, rel => rel.ManyToMany());
jsc.Map(human => human.Family, cm => { }, rel => rel.ManyToMany());
jsc.Bag(human => human.Pets, cm => { cm.Inverse = true; }, rel => rel.OneToMany());
jsc.Set(human => human.NickNames, cm =>
{
cm.Lazy = CollectionLazy.NoLazy;
cm.Sort();
});
jsc.Map(human => human.Addresses, cm => { }, rel => rel.Component(comp =>
{
comp.Property(address => address.Street);
comp.Property(address => address.City);
comp.Property(address => address.PostalCode);
comp.Property(address => address.Country);
comp.ManyToOne(address => address.StateProvince);
}));
});

map.Class<User, long>(sp => sp.Id, spid => spid.Generator = Generators.Foreign<User, Human>(u => u.Human), rc =>
{
rc.Property(user => user.UserName);
rc.OneToOne(user => user.Human, rm => rm.Constrained());
rc.List(user => user.Permissions, cm => { });
});

map.Class<Zoo, long>(zoo => zoo.Id, id => id.Generator = Generators.Native, rc =>
{
rc.Discriminator();
rc.Property(zoo => zoo.Name);
rc.Property(zoo => zoo.Classification);
rc.Map(zoo => zoo.Mammals, cm => { }, rel => rel.OneToMany());
rc.Map(zoo => zoo.Animals, cm => { cm.Inverse = true; }, rel => rel.OneToMany());
rc.Component(zoo => zoo.Address, comp =>
{
comp.Property(address => address.Street);
comp.Property(address => address.City);
comp.Property(address => address.PostalCode);
comp.Property(address => address.Country);
comp.ManyToOne(address => address.StateProvince);
});
});

map.Subclass<PettingZoo>(sc => { });

map.Class<StateProvince, long>(sp => sp.Id, spid => spid.Generator = Generators.Native, rc =>
{
rc.Property(sp => sp.Name);
rc.Property(sp => sp.IsoCode);
});

Behind the API

As you can read in this old post, NHibernate is translating the XML in its metadata, each time an XML is added to the configuration; instead add an XML you can add directly the metadata. Even if everything is there, since long time ago, ready to be used, in NH3.0.0 (the trunk) I have terminated a very old pending task: all binders (from XML to metadata) now are working using the deserialized state of the XML. To be short, now you have a series of partial POCOs generated from the XSD (the xml-schema) and you can fill it instead write an XML. In the code of this post you can see how create these classes and how use it to configure NH without use XML and without generate XML (high speed mapping).

The entry point

The entry point, in the Configuration class is this method:

/// <summary>
///
Add mapping data using deserialized class.
/// </summary>
/// <param name="mappingDocument">
Mapping metadata.</param>
/// <param name="documentFileName">
XML file's name where available; otherwise null.</param>
public void AddDeserializedMapping(HbmMapping mappingDocument, string documentFileName)

The Code

If you want play with the code you can download, through SVN, it from here.

If you want see the integration test this is the link.

If you have some questions… well… I’m here.

Happy coding!!!

10 comments:

  1. Nice! I always dis-liked xml where it was used for static configuration. In reality you rarely change mappings on the fly, so xml was like string hard-coding.

    It will be very very easy to create a custom attribute based mapping and utilize IMapper in the implementation.

    IMHO, attribute based mapping is one of the best since you immediatelly see in one place what properties are mapped to DB, and which are not

    ReplyDelete
  2. anybody know if fluent-nh will magrate to this new api?

    ReplyDelete
  3. Great stuff! Much more powerful than Fluent because it allows dynamical mapping - it will be especially useful for ANY mappings and such. I suppose this will also support modifying metadata obtained from hbm.xml files?

    ReplyDelete
  4. @Darius
    Matter of taste. I don't like to have a reference to NH in my domain.
    Btw you can create your attributes and then create "deserialized" classes directly (the part behind the API) without use another layer.

    ReplyDelete
  5. @hazzik
    I have sent a tweet to James, about the new method in the NH's configuration, few weeks ago.

    ReplyDelete
  6. @Boris
    Sure. You can deserialize an XML mapping and change what you want through deserialized classes and than add the result to the configuration.

    ReplyDelete
  7. Great ! I've Always hated XML configuration , although it can can be useful . But , i have a question . I've recently read about Nolics.net ORM. It seems to be dead project but i've liked an idea that configuration is done through external DSL , Something like this:
    class Table
    property id automatic primary key
    property name string
    ....
    I wander if there are some plans to implement something similar in NHibernate .
    Best Regards!

    ReplyDelete
  8. @Marco
    do you mean something like this http://nhmodeller.selfip.com/3/section.aspx/category/2 ?

    ReplyDelete
  9. @Marco Remius pardon me but Nolics is the worst crap I have ever touched and I only touched it to replace it with NHibernate.

    ReplyDelete
  10. @Mikael Henriksson
    I haven't sad that Nolics is god product , if it was god it woudn't be dead but alive and kicking , i've only sad that external DSL for maping configuration (which Nolics.net implementied in theirs product) is a god idea .
    @Fabio - http://nhmodeller.selfip.com/3/section.aspx/category/2
    That is exactly what I was talking about , to bad i haven't discovered it before . Thank You !

    ReplyDelete