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:
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/).
Fabio, fantastic!
ReplyDeleteExcellent work!
ReplyDeleteI know i should check the work first, but in advance can you set low level things like max_lo=100?
Great!
ReplyDeleteYou 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.
How does this compare/contrast to Fluent NHibernate?
ReplyDeleteGreat work Fabio... and great Puccini.
ReplyDelete@Gustavo
ReplyDeleteorm.PoidStrategies.Add(new HighLowPoidPattern(new {max_low = 100}));
@ajlopez
ReplyDeletePlease make another example, in your example the relation is the same for both properties.
@F Quednau
ReplyDeleteI don't understand the question, can you explain it a little bit more ?
Great work.... Look forward to getting into it
ReplyDeleteChapeau!
ReplyDeletewow
ReplyDelete...G R E A T ! ! !
Great work Fabio!
ReplyDeleteIs there a way to prevent a property to be persisted? I mean like a convention?
@José
ReplyDeleteThe actual impl is this:
public bool IsPersistentProperty(MemberInfo role)
{
return true;
}
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?
ReplyDeleteAbout 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 ;-) ).
ReplyDeleteSend 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.
Hi Fabio,
ReplyDeleteI can't download the project at http://code.google.com/p/codeconform/ with tortoise ...do you can?
thanks for all...
@Tonio
ReplyDeleteYes I can.
Hey Fabio,
ReplyDeleteNice 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...
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@Mohamed
ReplyDeleteMore than "compare/contrast" I will post about some ConfORM facts, leaving to you the comparison.
@Fabio
ReplyDeleteCool. Thanks a lot.
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."
ReplyDeleteIt 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.
@Losing
ReplyDeleteYou are not ConfORM.
is conform an alternative for fluent NH? anyway, fabio, you always keep an ace up your sleeve, good job!
ReplyDeleteHello Fabio,
ReplyDeleteIs there any chance to be ConfORM an official part of NHibernate in future?
This would be great in my opinion.
It will be a decision of NH's team.
ReplyDeletebtw I don't think so.
Hello Fabio.
ReplyDeleteI need somethink like save-update and delete, but I can`t find it in the Cascade enum...
Could you help me?
Can you send your question to the ConfORM's google group ?
ReplyDeleteThanks.