Try fast search NHibernate

04 February 2010

ConfORM: NHibernate un-Mapping

The “Map NHibernate using your API” post was matter a little discussion in the NHibernate’s development-list. The feedback was not so big but enough to begin the work in NHibernate trunk. Before start the implementation inside NHibernate I wanted to understand how should look some classes with the responsibility to create the mapping. The API proposed in the previous post is pretty good but the underlining implementation is too much strongly typed to be reused in a non-strongly-typed task.

I was thinking in begin a new proof-of-concept but two weeks ago somebody have asked me to write ~400 mappings of a domain created from various XSD… perhaps to join business requirement and Open-Source pleasure is not so hard.

Few months ago, after a discussion in NHibernate’s dev-list, I have reserved a project name in Google-Code… and… with “Nessun Dorma” here we go.

The ORM (“Nessun dorma! Tu pure, o, Principessa, nella tua fredda stanza”)

A domain, and even less a single class, can’t self explain its persistent representation. To be short, and avoid the discussion over a dream (ORuM), I need a way to define :

  • which is the strategy to represent a class hierarchy
  • which is the relation between classes
  • how manage cascade-actions (read it as: which are aggregate-root)

The result are these methods:

void TablePerClassHierarchy<TBaseEntity>();
void TablePerClass<TBaseEntity>();
void TablePerConcreteClass<TBaseEntity>();

void ManyToOne<TLeftEntity, TRigthEntity>();
void OneToOne<TLeftEntity, TRigthEntity>();
void ManyToMany<TLeftEntity, TRigthEntity>();

void Cascade<TFromEntity, TToEntity>(Cascade cascadeOptions);

Perhaps, some relations can be self discovered watching the domain but it isn’t so clear always.

The mapping to NHibernate (“ma il mio mistero é chiuso in me”)

The exigency of a mapping is, may be, more complicated… bah?!? “may be”… it is more complicated especially if you want use some ORM feature. Between the way I’m using to describe my domain-persistent-representation and the mapping needs I’m needing a contract.

public interface IDomainInspector
{
bool IsRootEntity(Type type);
bool IsComponent(Type type);
bool IsComplex(Type type);
bool IsEntity(Type type);

bool IsTablePerClass(Type type);
bool IsTablePerClassHierarchy(Type type);
bool IsTablePerConcreteClass(Type type);

bool IsOneToOne(Type from, Type to);
bool IsManyToOne(Type from, Type to);
bool IsManyToMany(Type role1, Type role2);
bool IsOneToMany(Type from, Type to);
bool IsHeterogeneousAssociations(MemberInfo member);
Cascade ApplyCascade(Type from, Type to);

bool IsPersistentId(MemberInfo member);
IPersistentIdStrategy GetPersistentIdStrategy(MemberInfo member);

bool IsPersistentProperty(MemberInfo role);
IDbColumnSpecification[] GetPersistentSpecification(MemberInfo role);

What happen if any mapping task will be based on this contract ? Which can be the way you can use to describe your domain ?

If I can write all mappings to NHibernate discovering anything needed basing in this contract your domain-descriptor can be API, DSL, Attributes, classes generated for EntityFramework (Ups!!! I should not say).

The Example (“Tramontate stelle! All’alba vinceró!”)

The domain is the same of the previous post where I have used an API to map each element:


DomainDiagram

The difference is that with ConfORM the mapping is this:

var orm = new ObjectRelationalMapper();

orm.TablePerClass<Animal>();
orm.TablePerClass<User>();
orm.TablePerClass<StateProvince>();
orm.TablePerClassHierarchy<Zoo>();

orm.ManyToMany<Human, Human>();
orm.OneToOne<User, Human>();

orm.PoidStrategies.Add(new NativePoidPattern());

Who wrote the mapping ?

ConfOrm does not generate XML mappings but you can easy create XMLs to check how ConfOrm work.

The code to see the XML is available in this example.

For the above mapping the XML is:

<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
namespace="ConfOrmExample.Domain"
assembly="ConfOrmExample"
xmlns="urn:nhibernate-mapping-2.2">
<
class name="User">
<
id name="Id" type="Int64">
<
generator class="native" />
</
id>
<
property name="UserName" />
<
one-to-one name="Human" />
<
list name="Permissions">
<
key column="user_key" />
<
list-index />
<
element type="String" />
</
list>
</
class>
<
class name="Animal">
<
id name="Id" type="Int64">
<
generator class="native" />
</
id>
<
property name="BodyWeight" />
<
set name="Offspring" inverse="true" cascade="all,delete-orphan">
<
key column="Mother" />
<
one-to-many class="Animal" />
</
set>
<
many-to-one name="Mother" cascade="all" />
<
many-to-one name="Father" cascade="all" />
<
property name="Description" />
<
many-to-one name="Zoo" />
<
property name="SerialNumber" />
</
class>
<
class name="StateProvince">
<
id name="Id" type="Int64">
<
generator class="native" />
</
id>
<
property name="Name" />
<
property name="IsoCode" />
</
class>
<
class name="Zoo">
<
id name="Id" type="Int64">
<
generator class="native" />
</
id>
<
discriminator />
<
property name="Name" />
<
property name="Classification" />
<
map name="Animals" inverse="true">
<
key column="Zoo" />
<
map-key type="String" />
<
one-to-many class="Animal" />
</
map>
<
map name="Mammals" inverse="true">
<
key column="Zoo" />
<
map-key type="String" />
<
one-to-many class="Mammal" />
</
map>
<
component class="Address" name="Address">
<
property name="Street" />
<
property name="City" />
<
property name="PostalCode" />
<
property name="Country" />
<
many-to-one name="StateProvince" />
</
component>
</
class>
<
joined-subclass name="Mammal" extends="Animal">
<
key column="animal_key" />
<
property name="Pregnant" />
<
property name="Birthdate" />
</
joined-subclass>
<
joined-subclass name="DomesticAnimal" extends="Mammal">
<
key column="mammal_key" />
<
many-to-one name="Owner" cascade="all" />
</
joined-subclass>
<
joined-subclass name="Cat" extends="DomesticAnimal">
<
key column="domesticanimal_key" />
</
joined-subclass>
<
joined-subclass name="Dog" extends="DomesticAnimal">
<
key column="domesticanimal_key" />
</
joined-subclass>
<
joined-subclass name="Reptile" extends="Animal">
<
key column="animal_key" />
<
property name="BodyTemperature" />
</
joined-subclass>
<
joined-subclass name="Lizard" extends="Reptile">
<
key column="reptile_key" />
</
joined-subclass>
<
subclass name="PettingZoo" extends="Zoo" />
<
joined-subclass name="Human" extends="Mammal">
<
key column="mammal_key" />
<
component class="Name" name="Name">
<
property name="First" />
<
property name="Initial" />
<
property name="Last" />
</
component>
<
property name="NickName" />
<
bag name="Friends" cascade="all,delete-orphan">
<
key column="human_key" />
<
many-to-many class="Human" />
</
bag>
<
bag name="Pets" inverse="true" cascade="all,delete-orphan">
<
key column="Owner" />
<
one-to-many class="DomesticAnimal" />
</
bag>
<
map name="Family" cascade="all,delete-orphan">
<
key column="human_key" />
<
map-key type="String" />
<
many-to-many class="Human" />
</
map>
<
property name="Height" />
<
property name="BigIntegerValue" />
<
property name="BigDecimalValue" />
<
property name="IntValue" />
<
property name="FloatValue" />
<
set name="NickNames">
<
key column="human_key" />
<
element type="String" />
</
set>
<
map name="Addresses">
<
key column="human_key" />
<
map-key type="String" />
<
composite-element class="Address">
<
property name="Street" />
<
property name="City" />
<
property name="PostalCode" />
<
property name="Country" />
<
many-to-one name="StateProvince" />
</
composite-element>
</
map>
</
joined-subclass>
</
hibernate-mapping>

Who wrote the mapping ?

Where is the code

ConfOrm is open source under LGPL and is hosted in Google-Code (check http://code.google.com/p/codeconform/).

28 comments:

  1. Excellent work!
    I know i should check the work first, but in advance can you set low level things like max_lo=100?

    ReplyDelete
  2. Great!

    You should support:

    ManyToOne(p -> p.Parent, typeof(Person));
    ManyToOne(p -> p.Friend, typeof(Person));

    and others alike

    that is, two types can be related in many ways, depending on some property.

    ReplyDelete
  3. How does this compare/contrast to Fluent NHibernate?

    ReplyDelete
  4. Great work Fabio... and great Puccini.

    ReplyDelete
  5. @Gustavo
    orm.PoidStrategies.Add(new HighLowPoidPattern(new {max_low = 100}));

    ReplyDelete
  6. @ajlopez
    Please make another example, in your example the relation is the same for both properties.

    ReplyDelete
  7. @F Quednau
    I don't understand the question, can you explain it a little bit more ?

    ReplyDelete
  8. Great work.... Look forward to getting into it

    ReplyDelete
  9. Great work Fabio!
    Is there a way to prevent a property to be persisted? I mean like a convention?

    ReplyDelete
  10. @José
    The actual impl is this:
    public bool IsPersistentProperty(MemberInfo role)
    {
    return true;
    }

    ReplyDelete
  11. What I really want is to be able to create more advanced strategies/conventions. For example I'd define a class as an aggregate root, all classes referenced by it that are not themselves aggregate roots will not be lazy loaded, versioning is against the aggregate root, the aggregate root is validated before persistence. Would that be possible?

    ReplyDelete
  12. About AggregateRoot and cascade-action+lazyLoad, yes it is possible and easy to implements extending the class ObjectRelationalMapper with an extension method or inheriting. The validation stuff is outside ConfORM target (probably... or may be not ;-) ).
    Send me and example of your domain and how you would map it or a more deeper explication and I'll study your situation and how do it with ConfORM-to-NHibernate.

    ReplyDelete
  13. Hi Fabio,
    I can't download the project at http://code.google.com/p/codeconform/ with tortoise ...do you can?

    thanks for all...

    ReplyDelete
  14. Hey Fabio,
    Nice stuff really. I think I have a question about Fluent NHibernate as well. Both ConfORM and Fluent NHibernate are answers to the same question: "How do I write NHibernate mapping in typed code not XML?".

    So, "How does this compare/contrast to Fluent NHibernate?" given the above context question...

    ReplyDelete
  15. Maybe I haven't understood what problem the things you're working on solves. FluentNH set out to avoid the writing of XML mappings and developed from there. What problem do you want to solve with your current work?

    ReplyDelete
  16. @Mohamed
    More than "compare/contrast" I will post about some ConfORM facts, leaving to you the comparison.

    ReplyDelete
  17. This is interesting, but there doesn't seem to be an easy way of saying "The order class corresponds to the NewOrder table. The Id property corresponds to the OrdId field."

    It seems like everything is aimed at conventions, which makes it hard to work with legacy databases.

    I far prefer the "just an API" approach to FNH's ClassMap tricks, though.

    ReplyDelete
  18. is conform an alternative for fluent NH? anyway, fabio, you always keep an ace up your sleeve, good job!

    ReplyDelete
  19. Hello Fabio,
    Is there any chance to be ConfORM an official part of NHibernate in future?

    This would be great in my opinion.

    ReplyDelete
  20. It will be a decision of NH's team.
    btw I don't think so.

    ReplyDelete
  21. Hello Fabio.
    I need somethink like save-update and delete, but I can`t find it in the Cascade enum...
    Could you help me?

    ReplyDelete
  22. Can you send your question to the ConfORM's google group ?
    Thanks.

    ReplyDelete