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
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!!!
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.
ReplyDeleteIt 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
anybody know if fluent-nh will magrate to this new api?
ReplyDeleteGreat 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@Darius
ReplyDeleteMatter 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.
@hazzik
ReplyDeleteI have sent a tweet to James, about the new method in the NH's configuration, few weeks ago.
@Boris
ReplyDeleteSure. You can deserialize an XML mapping and change what you want through deserialized classes and than add the result to the configuration.
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:
ReplyDeleteclass Table
property id automatic primary key
property name string
....
I wander if there are some plans to implement something similar in NHibernate .
Best Regards!
@Marco
ReplyDeletedo you mean something like this http://nhmodeller.selfip.com/3/section.aspx/category/2 ?
@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@Mikael Henriksson
ReplyDeleteI 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 !