Try fast search NHibernate

09 March 2010

ConfORM: “Mapping” Components

I’ll try to talk about how customize ConfORM but before I would show you its default behavior.

In the following examples I will show you how ConfORM will take care about the modifications you will do to your domain showing you the result XML-mapping.

The ConfORM mapping is the same for all firsts examples:

var orm = new ObjectRelationalMapper();
orm.TablePerClass<Person>();
var mapper = new Mapper(orm);

var nhconf = ConfigureSessionFactory();
nhconf.AddDeserializedMapping(mapper.CompileMappingFor(new[] { typeof(Person) }), "FullDomain");

basically it is just : orm.TablePerClass<Person>();

Mapping a class with components

The domain is this:

public class Person
{
public int Id { get; set; }
public Name Name { get; set; }
public Address Address { get; set; }
}
public class Name
{
public string First { get; set; }
public string Last { get; set; }
}
public class Address
{
public string Street { get; set; }
public int CivicNumber { get; set; }
}

The entity Person has two components, each one used just one time, and the result XML is:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
component class="Name" name="Name">
<
property name="First" />
<
property name="Last" />
</
component>
<
component class="Address" name="Address">
<
property name="Street" />
<
property name="CivicNumber" />
</
component>
</
class>

As you can see, ConfORM is using the default NHibernate’s naming-convention and nothing more.

Mapping a class with double usage of same component

Now I’ll change the class Person adding a new property of type Name:

public class Person
{
public int Id { get; set; }
public Name Name { get; set; }
public Name ShowBusinessAlias { get; set; }
public Address Address { get; set; }
}

ConfORM will take care about column naming only for the double usage of Name:

  <class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
component class="Name" name="Name">
<
property name="First" column="NameFirst" />
<
property name="Last" column="NameLast" />
</
component>
<
component class="Name" name="ShowBusinessAlias">
<
property name="First" column="ShowBusinessAliasFirst" />
<
property name="Last" column="ShowBusinessAliasLast" />
</
component>
<
component class="Address" name="Address">
<
property name="Street" />
<
property name="CivicNumber" />
</
component>
</
class>

Collection of components

Now I need to expose a collection of Address instead a single one (I should modify the Address class to implements equality-comparer but that is outside the scope of this post):

public class Person
{
private ISet<Address> addresses;
public Person()
{
addresses = new HashedSet<Address>();
}
public int Id { get; set; }
public Name Name { get; set; }
public ICollection<Address> Addresses { get { return addresses; } }
}

and again without touch the ConfORM mapping the result is:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
component class="Name" name="Name">
<
property name="First" />
<
property name="Last" />
</
component>
<
set name="Addresses" access="field.camelcase">
<
key column="person_key" />
<
composite-element class="Address">
<
property name="Street" />
<
property name="CivicNumber" />
</
composite-element>
</
set>
</
class>

Bidirectional relation

Now I’ll change the implementation of Name to have a bidirectional relation with Person:

public class Person
{
public int Id { get; set; }
public Name Name { get; set; }
public Name ShowBusinessAlias { get; set; }
public Address Address { get; set; }
}

public class Name
{
public Person Person { get; set; }
public string First { get; set; }
public string Last { get; set; }
}

and again without touch the ConfORM mapping the result is:

  <class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
component class="Name" name="Name">
<
parent name="Person" />
<
property name="First" column="NameFirst" />
<
property name="Last" column="NameLast" />
</
component>
<
component class="Name" name="ShowBusinessAlias">
<
parent name="Person" />
<
property name="First" column="ShowBusinessAliasFirst" />
<
property name="Last" column="ShowBusinessAliasLast" />
</
component>
<
component class="Address" name="Address">
<
property name="Street" />
<
property name="CivicNumber" />
</
component>
</
class>

A little bit of DDL sutff

For this example I’ll maintain the same implementation of Person and I will change the mapping.

public class Person
{
private ISet<Address> addresses;
public Person()
{
addresses = new HashedSet<Address>();
}
public int Id { get; set; }
public Name Name { get; set; }
public Name ShowBusinessAlias { get; set; }
public Address MainAddress { get; set; }
public ICollection<Address> Addresses { get { return addresses; } }
}
A little explication

To be short the class ObjectRelationalMapper is responsible for the mapping-drive, any mapping elements is defined/discovered by ObjectRelationalMapper. Who perform the final mapping is the class Mapper.

The dance

Even if the right place to define your standard length for strings is your custom Dialect, you can define your convention even using ConfORM:

mapper.AddPropertyPattern(mi => mi.GetPropertyOrFieldType() == typeof (string), pm => pm.Length(50));

and the mapping will be

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
component class="Name" name="Name">
<
property name="First" column="NameFirst" length="50" />
<
property name="Last" column="NameLast" length="50" />
</
component>
<
component class="Name" name="ShowBusinessAlias">
<
property name="First" column="ShowBusinessAliasFirst" length="50" />
<
property name="Last" column="ShowBusinessAliasLast" length="50" />
</
component>
<
component class="Address" name="MainAddress">
<
property name="Street" length="50" />
<
property name="CivicNumber" />
</
component>
<
set name="Addresses" access="field.camelcase">
<
key column="person_key" />
<
composite-element class="Address">
<
property name="Street" length="50" />
<
property name="CivicNumber" />
</
composite-element>
</
set>
</
class>

Now I want customize the length of Name.Last and Address.Street:

mapper.AddPropertyPattern(mi => mi.GetPropertyOrFieldType() == typeof (string), pm => pm.Length(50));
mapper.Customize<Name>(pcc => pcc.Property(n => n.Last, pm => pm.Length(60)));
mapper.Customize<Address>(pcc => pcc.Property(n => n.Street, pm => pm.Length(80)));

and the mapping will be

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
component class="Name" name="Name">
<
property name="First" column="NameFirst" length="50" />
<
property name="Last" column="NameLast" length="60" />
</
component>
<
component class="Name" name="ShowBusinessAlias">
<
property name="First" column="ShowBusinessAliasFirst" length="50" />
<
property name="Last" column="ShowBusinessAliasLast" length="60" />
</
component>
<
component class="Address" name="MainAddress">
<
property name="Street" length="80" />
<
property name="CivicNumber" />
</
component>
<
set name="Addresses" access="field.camelcase">
<
key column="person_key" />
<
composite-element class="Address">
<
property name="Street" length="80" />
<
property name="CivicNumber" />
</
composite-element>
</
set>
</
class>

Now I want a special configuration for the Name.Last but only when used in the property Person.ShowBusinessAlias, and for Address.Street but only when used in Person.MainAddress:

mapper.AddPropertyPattern(mi => mi.GetPropertyOrFieldType() == typeof(string), pm => pm.Length(50));
mapper.Customize<Name>(pcc => pcc.Property(n => n.Last, pm => pm.Length(60)));
mapper.Customize<Address>(pcc => pcc.Property(n => n.Street, pm => pm.Length(80)));
mapper.Class<Person>(classMapper =>
{
classMapper.Component(p => p.ShowBusinessAlias, cm => cm.Property(n => n.Last, pm => pm.Length(45)));
classMapper.Component(p => p.MainAddress, cm => cm.Property(n => n.Street, pm => pm.Length(150)));
});

and the mapping become

  <class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
component class="Name" name="Name">
<
property name="First" column="NameFirst" length="50" />
<
property name="Last" column="NameLast" length="60" />
</
component>
<
component class="Name" name="ShowBusinessAlias">
<
property name="First" column="ShowBusinessAliasFirst" length="50" />
<
property name="Last" column="ShowBusinessAliasLast" length="45" />
</
component>
<
component class="Address" name="MainAddress">
<
property name="Street" length="150" />
<
property name="CivicNumber" />
</
component>
<
set name="Addresses" access="field.camelcase">
<
key column="person_key" />
<
composite-element class="Address">
<
property name="Street" length="80" />
<
property name="CivicNumber" />
</
composite-element>
</
set>
</
class>

in addition I want apply two of my conventions for the name of the ID column and the name of “key” column of the collection:

mapper.AddPropertyPattern(mi => mi.GetPropertyOrFieldType() == typeof (string), pm => pm.Length(50));
mapper.Customize<Name>(pcc => pcc.Property(n => n.Last, pm => pm.Length(60)));
mapper.Customize<Address>(pcc => pcc.Property(n => n.Street, pm => pm.Length(80)));
mapper.Class<Person>(classMapper =>
{
classMapper.Component(p => p.ShowBusinessAlias, cm => cm.Property(n => n.Last, pm => pm.Length(45)));
classMapper.Component(p => p.MainAddress, cm => cm.Property(n => n.Street, pm => pm.Length(150)));
});
mapper.AddPoidPattern(mi => mi.Name == "Id", (mi, idm) => idm.Column(mi.ReflectedType.Name + "Id"));
mapper.AddCollectionPattern(mi => true, (mi, cm) => cm.Key(km => km.Column(mi.DeclaringType.Name + "Id")));

and the new mapping will result in

<class name="Person">
<
id name="Id" column="PersonId" type="Int32">
<
generator class="hilo" />
</
id>
<
component class="Name" name="Name">
<
property name="First" column="NameFirst" length="50" />
<
property name="Last" column="NameLast" length="60" />
</
component>
<
component class="Name" name="ShowBusinessAlias">
<
property name="First" column="ShowBusinessAliasFirst" length="50" />
<
property name="Last" column="ShowBusinessAliasLast" length="45" />
</
component>
<
component class="Address" name="MainAddress">
<
property name="Street" length="150" />
<
property name="CivicNumber" />
</
component>
<
set name="Addresses" access="field.camelcase">
<
key column="PersonId" />
<
composite-element class="Address">
<
property name="Street" length="80" />
<
property name="CivicNumber" />
</
composite-element>
</
set>
</
class>

As last the boss said that the table for Addresses should be named as “Person_Addresses”. Instead modify something I wrote and tested what I need to do is only add the new required mapping:

mapper.AddPropertyPattern(mi => mi.GetPropertyOrFieldType() == typeof (string), pm => pm.Length(50));
mapper.Customize<Name>(pcc => pcc.Property(n => n.Last, pm => pm.Length(60)));
mapper.Customize<Address>(pcc => pcc.Property(n => n.Street, pm => pm.Length(80)));
mapper.Class<Person>(classMapper =>
{
classMapper.Component(p => p.ShowBusinessAlias, cm => cm.Property(n => n.Last, pm => pm.Length(45)));
classMapper.Component(p => p.MainAddress, cm => cm.Property(n => n.Street, pm => pm.Length(150)));
});
mapper.AddPoidPattern(mi => mi.Name == "Id", (mi, idm) => idm.Column(mi.ReflectedType.Name + "Id"));
mapper.AddCollectionPattern(mi => true, (mi, cm) => cm.Key(km => km.Column(mi.DeclaringType.Name + "Id")));
mapper.Class<Person>(classMapper => classMapper.Set(p => p.Addresses, cm => cm.Table("Person_Addresses"), er => { }));

The last line is the one added and even if it seems a duplication of the Person definition there is no problem because ConfORM will merge it as expected, and the mapping will be:

<class name="Person">
<
id name="Id" column="PersonId" type="Int32">
<
generator class="hilo" />
</
id>
<
component class="Name" name="Name">
<
property name="First" column="NameFirst" length="50" />
<
property name="Last" column="NameLast" length="60" />
</
component>
<
component class="Name" name="ShowBusinessAlias">
<
property name="First" column="ShowBusinessAliasFirst" length="50" />
<
property name="Last" column="ShowBusinessAliasLast" length="45" />
</
component>
<
component class="Address" name="MainAddress">
<
property name="Street" length="150" />
<
property name="CivicNumber" />
</
component>
<
set name="Addresses" access="field.camelcase" table="Person_Addresses">
<
key column="PersonId" />
<
composite-element class="Address">
<
property name="Street" length="80" />
<
property name="CivicNumber" />
</
composite-element>
</
set>
</
class>

Conclusion

First I have maintained the ConfORM’s mapping fixed and you seen how ConfORM takes care about the modifications I was applying to the domain. Then I have defined my conventions and I have customized some particular stuff in some ‘generic’ and specialized places without follow neither a particular order nor organization of customization.

Are you ConfORM ?

When something exceeds your ability to understand how it works, this tool becomes magical.

3 comments:

  1. Shouldn't your classes be decorated with the "virtual" keyword? Just want to make sure its still required for NH.

    Thanks!!

    ReplyDelete
  2. That is not a matter that ConfORM should care.
    Who will care about it is NH itself, we done it there ;)

    ReplyDelete