Try fast search NHibernate

06 March 2010

ConfORM: Introduction example reloaded

For the last Alt.NET café I have asked some “domain” example challenge… well you know… your domain is your TOP SECRET and extract some “situation”, even changing names and removing behavior, is too hard because your domain is really complex.

Luckily there are some guys working in some “very little” and “simple” environment… One of those was Carlos Peix.

Carlos sent me the source and the expected mapping even if, to try ConfORM behavior, I need only the DLL and some little info. During the VAN I have not realized any thing done by ConfORM because I have used only the XML to compare the human-driven mapping to the ConfORM driven. In a moment we saw this in one of his classes:

private string _relyingPartyUri;
public Uri RelyingPartyUri
{
get { return new Uri(_relyingPartyUri); }
set { _relyingPartyUri = value.ToString(); }
}

Believe me that, for me, was a surprise when I saw the mapping created by ConfORM
<property name="RelyingPartyUri" access="field.camelcase-underscore" />


What? How can ConfORM understand that implementation and then map the field ?

Ok, perhaps I should study each piece of the Carlos’s little challenge and have a look to each little thing.

The domain (I'm breathing the silence tonight)


CarloDomain

Three entities, some properties here and there, a class (Credencial) that is not an entity, an enum, some relations and one bidirectional relation (Identidad<->Mapeo).

The mapping (The night is holding its breath)

During the VAN the intention was not implements some of ConfORM’s patterns nor appliers (I’ll explain patterns and appliers in some future post), so using the default behavior the mapping is:

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass<Aplicacion>();
orm.TablePerClass<Identidad>();
orm.TablePerClass<Mapeo>();
}

As example we have seen how apply a simple pattern that, in this case, is a simple Carlos’s convention (after talk with him I have changed it for this post):

public void RegisterPatterns(Mapper mapper)
{
mapper.AddPropertyPattern(
member => member.GetPropertyOrFieldType() == typeof (DateTime) && member.Name.StartsWith("Fecha"),
p => p.Type(NHibernateUtil.Date));
}

The convention say: for each property which type is DateTime, and the name starts with “Fecha”, apply the NHibernate’s type Date.
That is all !!

What happened (Just today I knew what was right)

First of all don’t forget what happened with the property showed at the begin of this post and then…

For the class Mapeo, which have relations with both Aplicacion and Identidad, the result was:

<class name="Mapeo">
<
id name="Id" access="nosetter.camelcase-underscore" type="Guid">
<
generator class="guid.comb" />
</
id>
<
many-to-one name="Identidad" />
<
many-to-one name="Aplicacion" />
<
property name="AppUserName" />
</
class>

So… not only ConfORM have discovered the two relations but even the ID, the type of the ID, the fact that the ID has no setter and its right filed-accessor. How? perhaps because Carlos implements his classes very similar as ConfORM can understand (even if I never seen how Carlos implements his domains).

hmm… and the bidirectional relation ? The implementation in the class is


private IList<Mapeo> _mapeos;
public virtual IList<Mapeo> Mapeos
{
get { return _mapeos; }
}

Don’t worry ConfORM can recognize it in your code and can map it as you should do

<bag name="Mapeos" access="nosetter.camelcase-underscore" inverse="true" cascade="all,delete-orphan">
<
key column="Identidad" on-delete="cascade" />
<
one-to-many class="Mapeo" />
</
bag>

aaaaahh ok, ok, but there is something else. In the class Identidad there are two properties of a class that is not an entity. The implementation in the class is:

private Credencial _credencialInterna;
public Credencial CredencialInterna
{
get { return _credencialInterna; }
set { _credencialInterna = value; }
}

private Credencial _credencialExterna;
public Credencial CredencialExterna
{
get { return _credencialExterna; }
set { _credencialExterna = value; }
}

It seems to be a double-usage of a component in the same class… right ? well, now guess how conform have mapped it ;) Before see the mapping, of this case, let me show you something else. What seems to be a component (the class Credencial) has four public properties which implementation is:

Are all properties persistent ? hmm… In my opinion there are only two persistent properties without a setter…

Resuming we have a double usage of a component (that mean we should do something with the column’s name to avoid columns duplication) and the component should have the two persistent properties… try to guess what happen using ConfORM:


  <component class="Credencial" name="CredencialInterna">
<
property name="Identificador" access="nosetter.camelcase-underscore" column="CredencialInternaIdentificador" />
<
property name="Estado" access="nosetter.camelcase-underscore" column="CredencialInternaEstado" />
</
component>
<
component class="Credencial" name="CredencialExterna">
<
property name="Identificador" access="nosetter.camelcase-underscore" column="CredencialExternaIdentificador"/>
<
property name="Estado" access="nosetter.camelcase-underscore" column="CredencialExternaEstado" />
</
component>

The DDL part (Nobody loves me, nobody loves me enough, Enough to save me, oh no)

So far interesting stuff, no ? but what about the DDL part of the mapping ? where are columns sizes, index, unique-index and so on ?
Using ConfORM you don’t need to map everything, about a class, in a single place altogether; you can map different concerns in different places (don’t worry ConfORM will adjust it).


public void CustomizeMapper(Mapper mapper)
{
CustomizeDDL(mapper);
}

private void CustomizeDDL(Mapper mapper)
{
mapper.Class<Aplicacion>(app =>
{
app.Property(a => a.Nombre, pm =>
{
pm.Unique(true);
pm.Length(50);
});
app.Property(a => a.RelyingPartyUri, pm =>
{
pm.Unique(true);
pm.Length(200);
});
});
mapper.Class<Identidad>(idt =>
{
idt.Property(a => a.Cuil, pm => pm.Length(11));
idt.Property(a => a.Email, pm => pm.Length(50));
idt.Property(a => a.TipoDocumento, pm => pm.Length(3));
idt.Property(a => a.Documento, pm => pm.Length(8));
idt.Property(a => a.Sexo, pm => pm.Length(1));
idt.Property(a => a.Nombres, pm => pm.Length(50));
idt.Property(a => a.Apellidos, pm => pm.Length(50));
});
mapper.Customize<Credencial>(cre => cre.Property(x => x.Identificador, pm => pm.Length(50)));
}

This part will be what you will see soon in NHibernate3.0 (the Loquacious mapping, ReSharper friendly).

The final result (The moon remains constant in the sky)

A possible problem, using tools as ConfORM, is the final result and how we can use it for support requests in various forums. Even if ConfORM does not generate XML you must have the ability to talk with us, in a forum, and send us a human readable mapping… believe me that a lot of NHibernate’s users can read the follow mapping:

  <class name="Mapeo">
<
id name="Id" access="nosetter.camelcase-underscore" type="Guid">
<
generator class="guid.comb" />
</
id>
<
many-to-one name="Identidad" />
<
many-to-one name="Aplicacion" />
<
property name="AppUserName" />
</
class>

<
class name="Aplicacion">
<
id name="Id" access="nosetter.camelcase-underscore" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Nombre" length="50" unique="true" />
<
property name="RelyingPartyUri" access="field.camelcase-underscore" length="200" unique="true" />
<
property name="UsaCredencialInterna" />
</
class>

<
class name="Identidad">
<
id name="Id" access="nosetter.camelcase-underscore" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Cuil" length="11" />
<
property name="Email" length="50" />
<
property name="TipoDocumento" length="3" />
<
property name="Documento" length="8" />
<
property name="FechaNacimiento" type="Date" />
<
property name="Sexo" length="1" />
<
property name="Nombres" length="50" />
<
property name="Apellidos" length="50" />
<
property name="Activo" />
<
component class="Credencial" name="CredencialInterna">
<
property name="Identificador" access="nosetter.camelcase-underscore" column="CredencialInternaIdentificador" length="50" />
<
property name="Estado" access="nosetter.camelcase-underscore" column="CredencialInternaEstado" />
</
component>
<
component class="Credencial" name="CredencialExterna">
<
property name="Identificador" access="nosetter.camelcase-underscore" column="CredencialExternaIdentificador" length="50" />
<
property name="Estado" access="nosetter.camelcase-underscore" column="CredencialExternaEstado" />
</
component>
<
bag name="Mapeos" access="nosetter.camelcase-underscore" inverse="true" cascade="all,delete-orphan">
<
key column="Identidad" on-delete="cascade" />
<
one-to-many class="Mapeo" />
</
bag>
</
class>

Are you ConfORM ?

There are times when a simple piece of code seems to have a soul.

P.S. the song was “Come into My life”.

Update (2010/03/06)

There is something else… as you can see the entity Identidad seems to have a collection of Aplicacion (property Aplicaciones) but that collection is not in the mapping… ehm… perhaps it is because its implementation is the follow:

public virtual IList<Aplicacion> Aplicaciones
{
get
{
var aplicaciones = new List<Aplicacion>();

foreach (var mapeo in Mapeos)
aplicaciones.Add(mapeo.Aplicacion);

return aplicaciones;
}
}

Perhaps would be interesting to find a way to map it with access=”none” but there is no request in such direction (so far).

11 comments:

  1. Hello Fabio,
    Very good approach. Are you planning merge Fluent-Nhibernate into this?

    ReplyDelete
  2. Hi Fabio, the orm.TablePerClass() calls in the MapDomain method seems to be to specify the inheritance mapping strategy but here you don't have inheritance... why do you need to specify those?

    ReplyDelete
  3. @German
    because that is the name in ORM theory and because an overload is:
    orm.TablePerClass(new[] { typeof(Aplicacion), typeof(Identidad), typeof(Mapeo) });

    Or this other:
    orm.TablePerClass(typeof(CarlosPeix).Assembly.GetTypes().Where(t => t.BaseType == typeof(Entity)));

    ReplyDelete
  4. @Rodrigo Sendin
    I'm planning what ?

    ReplyDelete
  5. @Rodrigo Sendin
    I'm planning what ?

    ReplyDelete
  6. @German
    On Friday (private chat) I pointed the very same issue to Fabio. I even recommended a Map() method with excatly the same implementation of TablePerClass(). He rules on the API, though :)

    ReplyDelete
  7. @Carlos
    The API of the class ObjectRelationalMapper is only one API. I hope too see a lot others API, very easy to implements with ConfORM.
    For example a DDD based API, or no API but attributes...
    ConfORM es de goma!!

    ReplyDelete
  8. @Carlos Peix
    I'll... when I want and if I'll need it
    LOL

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Hi, the Googles aren't helping me so I'll ask here:

    How do I hook up an old skool IIdentifierGenerator using the new Mapping-By-Code system in NH3+? I just need somewhere to map my Id(x => SpecialId, smth => typeof(MyGenerators.myCoolGenerator)) or something. But now Instead we have seemingly useless IIdMapper and IGeneratorDef interfaces that offer me no obvious opportunity to associate the code with my custom generator. How does this actually work these days?

    ReplyDelete