NHibernate 2.1 Trunk - Entity-Name, Some Inheritance and Dynamic Component
I have been experimenting with the NHibernate trunk today to test the feasibility of a dynamic persistence scenario. To follow this blog post it’s probably a good idea to read my previous post about dynamic-component too and to have a version of the NHibernate trunk to play with. To get the latest trunk point your subversion client at https://nhibernate.svn.sourceforge.net/svnroot/nhibernate/trunk/nhibernate/ and follow the build instructions included.
Working with a Person entity again:
1:
2: namespace Blog.Example.Domain
3: {
4: public class Person
5: {
6: public virtual int Id { get; protected set; }
7:
8: public Person()
9: {
10: Details = new Dictionary<string, object>();
11: }
12:
13: public virtual string Name { get; set; }
14: public virtual string Country { get; set; }
15: public virtual IDictionary Details { get; protected set; }
16:
17:
18: }
19: }
I need this Person entity to persist across multiple tables, one base table called Person and then a separate table to store the information in the Details property, this separate table will be different depending on the Country property of the Person.
So a Person object representing “Tony Blair” with UK set a country would be persisted over the tables Person and PersonUK, and a Person object representing “George Bush” would be persisted over the tables Person and PersonUS, people from different countries will have different information stored in the Details property bags which will be persisted to explicit database columns (i.e. the database schema is not dynamic in nature). As an Nhibernate user you’re probably recognising a possible solution of using joined-subclass and creating two empty derived types called PersonUK and PersonUS and using a Country as the database level discriminator and obviously the Type as the discriminator in the object model. In a normal domain model this would suffice rather well, but in our requirements we need to define new types of Person without recompilation, essentially at runtime we might need to setup mappings for a Person entity to represent french people that persists across Person and Person_FR tables and at such point we do not have a PersonFrench class (True, we could probably create class for each country to somewhat future proof this scenario but my actual real-world version of this is a little more convoluted unfortunately).
This is where the current general availability release of NHibernate starts to show some weaknesses, but not to worry they have been address with the trunk and forthcoming in the 2.1 GA release. In the current release one can not map the same type to a different choice of tables, one will encounter DuplicateMappingException, as NHibernate works heavily around using the Type to identify which entity working its with, at this point the options dwlindle to forking NHibernate to handle types generated at runtime or generate and persist an assembly before firing up or replacing a SessionFactory, both are far from ideal and will probably throw further roadblocks later in the project. If it feels bad it probably is!
The latest trunk introduces a new mapping attribute the works on several class related mapping elements called entity-name that basically assigns as an identifier for each mapping strategy, this allows one to have, for lack of better terminology, different profiles for persisting the same entity type in different ways for example to different tables and and even with different choice of properties. We use entity-name on the different joined subclasses of Person rather than the CLR type of subclass, in our case we do not even use a different class in our object model, since our Person domain object can represent all our different styles of Person thanks to the property bag. We use the element-name to define different profiles mapping different properties to our dynamic-component based on the a country identifier.
This is our new mapping file, notice the inclusion of entity-name on joined-subclass:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3: assembly="Blog.Example.Domain"
4: namespace="Blog.Example.Domain"
5: >
6: <class name="Person" table="Person" dynamic-update="true">
7: <id name="Id" type="Int32" column="PersonId">
8: <generator class="identity" />
9: </id>
10: <discriminator column="Country"/>
11:
12: <property name="Name" />
13: <property name="Country" column="Country" />
14:
15: <joined-subclass check="none" name="Person" entity-name="US" table="PersonUS">
16: <key column="PersonId" not-null="true"></key>
17: <dynamic-component name="Details">
18: <property name="NumberOfLawSuitsFilled" type="Int32"/>
19: <property name="SocialSecurityNumber" type="String"/>
20: </dynamic-component>
21: </joined-subclass>
22:
23: <joined-subclass check="none" name="Person" entity-name="UK" table="PersonUK">
24: <key column="PersonId" not-null="true"></key>
25: <dynamic-component name="Details">
26: <property name="DVLAReference" type="String"/>
27: <property name="NationalInsuranceNumber" type="String"/>
28: </dynamic-component>
29: </joined-subclass>
30:
31: </class>
32: </hibernate-mapping>
Using the mapping above the same Person entity we defined at the start of this blog can now represent multiple types of Person, persisted across different tables that isn’t depended on new derived types to satisfy NHibernate. To handle our dynamic requirement we use an interceptor to tell NHibernate what entity-name profile to use in the persistence process (the retrieval identifies which joined-subclass to use by the country database level discriminator).
1: using Blog.Example.Domain;
2: using NHibernate;
3:
4: namespace Blog.Example.Host
5: {
6: public class PersonInterceptor : EmptyInterceptor
7: {
8: public override string GetEntityName(object entity)
9: {
10: Person person = entity as Person;
11:
12: if (person != null)
13: {
14: return person.Country;
15: }
16:
17: return base.GetEntityName(entity);
18: }
19:
20: }
21: }
In our scenario we’ll amend the mapping file on the fly adding new joined-sublass/element-name parts when we’re notified of a new type of Person and the metadata associated with it (columns/properties/table-name), the above combination affords us this without creating new classes or amending our domain object model. We can even regenerate the sessionfactory on demand during our application lifecycle, allowing our database schema to evolve (should you be unfortunately be presented with such legacy scenarios), without the need for recompilation or even a restart.
In a nutshell this is a pretty cool addition and can be used in many different ways, in a more simple example one could create different profiles with different fetching strategies, e.g. lazy loading on relations for web app and all eagerly fetched on internal reporting etc.., in the same mapping assembly. The new version of NHibernate provides overloads on most of Session method to specify the entity-name profile in situations where you don’t want to use an Interceptor to determine this, this explicit way has limitations when it comes different entity-names on cascading saves and updates.
You can download the Visual Studio 2008 Solution I’ve put together with a working example to get more hands-on idea. You’ll need to run the blogexample.sql script in the artifacts solution folder (relative root folder on file system) against your local SQL Express instance, and/or edit the hibernate.cfg.xml accordingly. Enjoy.
Good work! Thank you!
I always wanted to write in my site something like that. Can I take part of your post to my blog?
Of course, I will add backlink?
Sincerely, Reader
Your Reader
28 Jan 09 at 12:14 am