Try fast search NHibernate

16 March 2010

ConfORM: “Mapping” Asp.Net Membership


In an application we are going to use ConfORM as way to map our domain for NHibernate. The application is composed by various modules and the first module where we want substitute “the other way to map” with ConfORM, is our implementation of Asp.NET Membership Provider (the application Expo Blox).
This is a good proof of ConfORM because we can’t take decisions about Table/Column naming nor anything else strictly related with the DB. In addition I have asked to Michele don’t send me the source but only assemblies. The result is that I can’t touch neither the DB nor the entities implementation.


Introduction

ConfORM does not have the limitation of define anything concerning a class in a single place and you can organize the mapping by concern. As basic template, for each module, we will use this interface :
public interface IModuleMapping
{
void DomainDefinition(ObjectRelationalMapper orm);
void RegisterPatterns(Mapper mapper);
void Customize(Mapper mapper);
IEnumerable<Type> GetEntities();
}

Initial Questions
  1. Which are the Root entities of this module and which is the strategy to represent the hierarchy ?
  2. Which are many-to-many, one-to-one ?
  3. Cascades… aggregate-root ?
  4. Something to say about the POID ?
  5. Something to say about the collection ? I mean… are you specifying only ICollection<T>, for property and field, even when the collection is an ISet<T> ?
With his answers the implementation of IModuleMapping become:
public class MembershipModuleMapping: IModuleMapping
{
private readonly Type[] tablePerClassRootEntities = new[] { typeof(User), typeof(Role), typeof(Application) };

#region Implementation of IModuleMapping

public void DomainDefinition(ObjectRelationalMapper orm)
{
orm.TablePerClass(tablePerClassRootEntities);
orm.ManyToMany<User, Role>();

orm.Patterns.PoidStrategies.Add(new GuidPoidPattern());
orm.Patterns.Sets.Add(new SetWhenGenericCollectionPattern());
}

public void RegisterPatterns(Mapper mapper)
{
}

public void Customize(Mapper mapper)
{
}

public IEnumerable<Type> GetEntities()
{
return typeof(User).Assembly.GetTypes().Where(t => typeof(BaseEntity).IsAssignableFrom(t));
}

#endregion
}
The Michele’s surprise is that with four lines we have a correct mapping but… “Fabio, We can’t follow the NHibernate’s convention for columns-naming , nor tables-naming, nor columns size, and nor for the type of DateTime.”

Follow Questions

  1. Which is the matter with DateTime ?
  2. Which are columns/table naming convention and specifications ?
Answers
  1. For all DateTime of this module we are using a custom type (SqlServerDateTimeUserType).
  2. Have a look to the DB defined by Microsoft, there you can find what you need.

To solve the answer-1

public void RegisterPatterns(Mapper mapper)
{
PersistentTypesPatterns(mapper);
}

private void PersistentTypesPatterns(Mapper mapper)
{
mapper.AddPropertyPattern(
mi => IsMemberOfClassOfMembershipProvider(mi) && mi.GetPropertyOrFieldType() == typeof(DateTime),
pm => pm.Type<SqlServerDateTimeUserType>());
}

private bool IsMemberOfClassOfMembershipProvider(MemberInfo mi)
{
return mi.ReflectedType.Assembly == typeof(User).Assembly;
}
For the answer-2
First of all some methods to group mappings by concern
public void RegisterPatterns(Mapper mapper)
{
PersistentTypesPatterns(mapper);
ColumnsNamingPatterns(mapper);
DDLPatterns(mapper);
}

public void Customize(Mapper mapper)
{
CustomizeTables(mapper);
CustomizeColumns(mapper);
}
I saw that most of columns follows the NHibernate’s convention where the mapped property-name is the name of the column. The columns where the NHibernate’s convention does not work are few and alls follows another convention that will be valid only for this module (in the others we will use the clear NHibernate’s convention):
private void ColumnsNamingPatterns(Mapper mapper)
{
mapper.AddPoidPattern(mi => IsMemberOfClassOfMembershipProvider(mi) && mi.Name == "Id",
(mi, idm) => idm.Column(mi.ReflectedType.Name + "Id"));
mapper.AddCollectionPattern(IsMemberOfClassOfMembershipProvider,
(mi, cm) => cm.Key(km => km.Column(mi.DeclaringType.Name + "Id")));
mapper.AddManyToOnePattern(IsMemberOfClassOfMembershipProvider,
(mi, idm) => idm.Column(mi.GetPropertyOrFieldType().Name + "Id"));
}

Another fact is that most of string-properties are represented with VARCHAR(256) (old school string sizing):

private void DDLPatterns(Mapper mapper)
{
mapper.AddPropertyPattern(
mi => mi.GetPropertyOrFieldType() == typeof(string) && IsMemberOfClassOfMembershipProvider(mi),
pm => pm.Length(256));
}

I can’t see any other pattern in the DB.
Now I can implements the method to customize the name of all tables involved as required by Asp.NET Membership:

private void CustomizeTables(Mapper mapper)
{
mapper.Class<User>(cm => cm.Table("aspnet_Users"));
mapper.Class<Role>(cm => cm.Table("aspnet_Roles"));
mapper.Class<Application>(cm => cm.Table("aspnet_Applications"));
mapper.JoinedSubclass<Entities.Membership>(cm => cm.Table("aspnet_Membership"));

mapper.Customize<User>(pcc => pcc.Collection(role => role.Roles, cpm => cpm.Table("aspnet_UsersInRoles")));
mapper.Customize<Role>(pcc => pcc.Collection(role => role.Users, cpm => cpm.Table("aspnet_UsersInRoles")));
}
As last I can map the other columns stuff :
private void CustomizeColumns(Mapper mapper)
{
mapper.Customize<User>(pcc =>
{
pcc.Property(role => role.UserName, pm => pm.NotNullable(true));
pcc.Property(role => role.LoweredUserName, pm => pm.NotNullable(true));
pcc.Property(role => role.MobileAlias, pm => pm.Length(16));

pcc.ManyToOne(role => role.Application, mtom => mtom.NotNullable(true));
});
mapper.Customize<Role>(pcc =>
{
pcc.Property(role => role.RoleName, pm => pm.NotNullable(true));
pcc.Property(role => role.LoweredRoleName, pm => pm.NotNullable(true));
pcc.ManyToOne(role => role.Application, mtom => mtom.NotNullable(true));
});
mapper.Customize<Application>(pcc =>
{
pcc.Property(app => app.ApplicationName, pm => pm.NotNullable(true));
pcc.Property(app => app.LoweredApplicationName, pm => pm.NotNullable(true));
});
mapper.Customize<Payoff.Membership.Entities.Membership>(pcc =>
{
pcc.Property(membership => membership.Password, pm =>
{
pm.Length(128);
pm.NotNullable(true);
});
pcc.Property(membership => membership.PasswordSalt, pm =>
{
pm.Length(128);
pm.NotNullable(true);
});
pcc.Property(membership => membership.MobilePIN, pm => pm.Length(16));
pcc.Property(membership => membership.PasswordAnswer, pm => pm.Length(128));
pcc.Property(membership => membership.Comment, pm => pm.Length(3000));
});

mapper.JoinedSubclass<Payoff.Membership.Entities.Membership>(cm => cm.Key(km => km.Column("UserId")));
}

Conclusion

Even when the DB was defined by somebody else and the classes implementations was defined by somebody else, ConfORM pass the test and allow me to add chunks of mapping with new discovered/needed matters without modify existing mappings and, over all, I can map by concern and not class by class.

15 March 2010

ConfORM: “Mapping” Many-To-Many

In the last two post about ConfORM you saw how ConfORM follow you and your domain applying well know mapping-pattern without ask you to know it.

In this post you will see how ConfORM simplify the mapping process without need to know all NHibernate’s mapping elements.

Mapping for unidirectional association

The mapping for all unidirectional examples is:

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(Person), typeof(Address) });
orm.ManyToMany<Person, Address>();
}

To set the cascade I can choose two ways:

  • by ObjectRelectionalMapper
  • by generic-customizer

Using the ObjectRelectionalMapper way I can define the cascade whichever is the relation from Person to Address:

orm.Cascade<Person, Address>(Cascade.All);
In these examples I will use the generic-customizer. The generic-customizer allows some customizations without know exactly which will be the real mapping. You don’t need to know if the class will be mapped as class,subclass,joined-subclass,union-subclass or a simple component. In the same way you can customize a collection without need to know if it will be a bag,set,array,list or a map(dictionary). The customization is:

public void CustomizeMapper(Mapper mapper)
{
mapper.Customize<Person>(cc => cc.Collection(person => person.Addresses, cm => cm.Cascade(Cascade.All)));
}

Unidirectional using Set<T>

The entities:

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

public class Address
{
public Guid Id { get; set; }
public string Street { get; set; }
public int CivicNumber { get; set; }
}

The mapping will be:

<class name="Person">
<
id name="Id" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Name" />
<
set name="Addresses" access="field.camelcase" cascade="all">
<
key column="person_key" />
<
many-to-many class="Address" />
</
set>
</
class>
<
class name="Address">
<
id name="Id" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Street" />
<
property name="CivicNumber" />
</
class>

As you can see, ConfORM have chose the right collection type, the right accessor and have applied the cascade.

Unidirectional using Bag<T>

The class modified:

public class Person
{
private ICollection<Address> addresses;
public Person()
{
addresses = new List<Address>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Address> Addresses
{
get { return addresses; }
}
}

The new mapping:

<class name="Person">
<
id name="Id" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Name" />
<
bag name="Addresses" access="nosetter.camelcase" cascade="all">
<
key column="person_key" />
<
many-to-many class="Address" />
</
bag>
</
class>

What I have done was only change the field and the constructor and again ConfORM have chose the right collection type, the right accessor and have applied the cascade.

Unidirectional using List<T>

The class modified:

public class Person
{
private IList<Address> addresses;
public Person()
{
addresses = new List<Address>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<Address> Addresses
{
get { return addresses; }
}
}

The new mapping:

<class name="Person">
<
id name="Id" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Name" />
<
list name="Addresses" access="field.camelcase" cascade="all">
<
key column="person_key" />
<
list-index />
<
many-to-many class="Address" />
</
list>
</
class>

Again ConfORM have chose the right collection type, the right accessor and have applied the cascade.

If I need to customize the name of the column of the list-index instead usage the NHibernate’s convention (note: the naming convention is applied by NHibernate and not by ConfORM) I can’t do it by generic-customizer because the list-index is specific of lists collection (how do it through pattern-applier and/or through specific-customizer will be a matter of another post).

Unidirectional using Map<TK,TV>

The first change is using the Address as value of the dictionary:

public class Person
{
private IDictionary<string, Address> addresses;
public Person()
{
addresses = new Dictionary<string, Address>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public IDictionary<string, Address> Addresses
{
get { return addresses; }
}
}

The new mapping:

<class name="Person">
<
id name="Id" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Name" />
<
map name="Addresses" access="nosetter.camelcase" cascade="all">
<
key column="person_key" />
<
map-key type="String" />
<
many-to-many class="Address" />
</
map>
</
class>

Now I’ll change the class in order to use the Address as the key of the dictionary:

public class Person
{
private IDictionary<Address, string> addresses;
public Person()
{
addresses = new Dictionary<Address, string>();
}
public Guid Id { get; set; }
public string Name { get; set; }
public IDictionary<Address, string> Addresses
{
get { return addresses; }
}
}

The new mapping:

<class name="Person">
<
id name="Id" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Name" />
<
map name="Addresses" access="nosetter.camelcase" cascade="all">
<
key column="person_key" />
<
map-key-many-to-many class="Address" />
<
element type="String" />
</
map>
</
class>

Resuming unidirectional many-to-many

Maintaining the same identical ConfORM-mapping (3 lines) I have changed radically the class implementation and ConfORM follows my needs.

Bidirectional many-to-many

In this case I will use a classic situation:

public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
public ISet<Role> Roles { get; set; }
}

public class Role
{
public Guid Id { get; set; }
public string Name { get; set; }
public ISet<User> Users { get; set; }
}

As you can imagine even in this case the mapping is composed by two lines:

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(User), typeof(Role) });
orm.ManyToMany<User, Role>();
}

As you can read in NHibernate’s reference manual, the bidirectional many-to-many follows a specific rule about the usage of “inverse”. If you don’t have time to read the manual, for this case, don’t worry because ConfORM know it and the mapping is:

  <class name="User">
<
id name="Id" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Name" />
<
set name="Roles" table="RoleUser">
<
key column="user_key" />
<
many-to-many class="Role" />
</
set>
</
class>
<
class name="Role">
<
id name="Id" type="Guid">
<
generator class="guid.comb" />
</
id>
<
property name="Name" />
<
set name="Users" table="RoleUser" inverse="true">
<
key column="role_key" />
<
many-to-many class="User" />
</
set>
</
class>

In this mappings you can see two pattern-applier working:

  1. BidirectionalManyToManyTableApplier who is responsible to set the same table for both sides
  2. BidirectionalManyToManyInverseApplier who is responsible to set the inverse-end

Conclusion

In this post, more than show you how many magic-mapping you can do with 2 or 3 lines, I have introduced two new words of ConfORM as generic-customizer and pattern-applier.

Are you ConfORM ?

I don’t need to change myself to fit the framework, it fits to me!

14 March 2010

ConfORM: “Mapping” One-To-One

In the previous post (“Mapping” Components) you saw how ConfORM can follow your domain-model changes and apply the correct mapping without addition effort.

In this post I’ll show you the same but using a one-to-one association.

Unidirectional one-to-one

The domain:

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

public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public int CivicNumber { get; set; }
}

As you can see there is a unidirectional relation between Person and Address and I’ll map it, in ConfORM, as a one-to-one in the follow way:

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(Person), typeof(Address) });
orm.OneToOne<Person, Address>();
}

In NHibernate a unidirectional one-to-one follows a specific mapping-pattern where the association is mapped as a many-to-one with a unique-key… in practice exactly as ConfORM map it:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Name" />
<
many-to-one name="Address" unique="true" cascade="all" />
</
class>
<
class name="Address">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Street" />
<
property name="CivicNumber" />
</
class>

If, in addition, you want apply even eager-fetch, when you get the instance by ID, you can customize the mapping in this way:

public void CustomizeMapper(Mapper mapper)
{
mapper.Class<Person>(cm => cm.ManyToOne(person => person.Address, mtom => mtom.Fetch(FetchMode.Join)));
}

to obtain this result:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Name" />
<
many-to-one name="Address" unique="true" cascade="all" fetch="join" />
</
class>
<
class name="Address">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Street" />
<
property name="CivicNumber" />
</
class>

There are two ways to customize the soft-cascade:

From where you describe your domain (the class ObjectRelationalMapper)

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(Person), typeof(Address) });
orm.OneToOne<Person, Address>();
orm.Cascade<Person, Address>(Cascade.Persist | Cascade.Remove);
}

or from where you can customize any mapping-element (the class Mapper)

public void CustomizeMapper(Mapper mapper)
{
mapper.Class<Person>(cm => cm.ManyToOne(person => person.Address, mtom =>
{
mtom.Fetch(FetchMode.Join);
mtom.Cascade(Cascade.Persist | Cascade.Remove);
}));
}

In both cases the result mapping, for the property Address, will be:

    <many-to-one name="Address" unique="true" cascade="save-update, persist,delete" fetch="join" />

Bidirectional one-to-one (primary key association)

The domain:

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

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

This time I have added a property of type Person to the Address of the previous sample but the mapping does not change:

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(Person), typeof(Address) });
orm.OneToOne<Person, Address>();
}

what will change is how ConfORM map it to NHibernate:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Name" />
<
one-to-one name="Address" cascade="all" />
</
class>
<
class name="Address">
<
id name="Id" type="Int32">
<
generator class="foreign">
<
param name="property">Person</param>
</
generator>
</
id>
<
one-to-one name="Person" constrained="true" />
<
property name="Street" />
<
property name="CivicNumber" />
</
class>

As you can see, using exactly the same mapping line (note that “line” is not plural), ConfORM can recognize the new bidirectional relation and apply the mapping-pattern with the foreign-generator and the constrain in Address and the one-to-one in Person (previously was many-to-one because unidirectional).

With the line

orm.OneToOne<Person, Address>();

I have chose Person as the master-object, you can invert the master-objet, to Address, inverting the type-arguments in this way:

orm.OneToOne<Address, Person>();

Bidirectional one-to-one (foreign key association)

If you are not completely sure that the association is and will be a one-to-one forever, instead a primary key association, in NHibernate, you should follow another mapping-pattern representing the relation, in the master-object, as a many-to-one.

The mapping in ConfORM is:

public void MapDomain(ObjectRelationalMapper orm)
{
orm.TablePerClass(new[] { typeof(Person), typeof(Address) });
orm.ManyToOne<Person, Address>();
orm.OneToOne<Address, Person>();
}

In this case, with only those two lines, ConfORM can recognize what you want do and apply a different mapping-pattern:

<class name="Person">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
property name="Name" />
<
many-to-one name="Address" unique="true" cascade="all" />
</
class>
<
class name="Address">
<
id name="Id" type="Int32">
<
generator class="hilo" />
</
id>
<
one-to-one name="Person" cascade="all" property-ref="Address" />
<
property name="Street" />
<
property name="CivicNumber" />
</
class>

As you can see in Address we have the one-to-one with the property-ref and in Person we have the unique-constraint.

Conclusion

You have seen how ConfORM not only simplifies your mapping process but even how ConfORM follows your domain changes and applies correct mapping-patterns (in practice the same you saw in the previous post).

Are you ConfORM ?

I don’t need to change myself to fit the framework, it fits to me!

11 March 2010

ConfORM : DDD API, Proposals ?

The ConfORM architecture allows a substitution of the “mapping-elements-drive”.

In the actual implementation I’m using a class named ObjectRelationalMapper where I’m using ORM terminology:

public interface IObjectRelationalMapper
{
void TablePerClass(IEnumerable<Type> baseEntities);
void TablePerClassHierarchy(IEnumerable<Type> baseEntities);
void TablePerConcreteClass(IEnumerable<Type> baseEntities);
void TablePerClassHierarchy<TBaseEntity>() where TBaseEntity : class;
void TablePerClass<TBaseEntity>() where TBaseEntity : class;
void TablePerConcreteClass<TBaseEntity>() where TBaseEntity : class;

void Component<TComponent>();
void Complex<TComplex>();

void Poid<TEntity>(Expression<Func<TEntity, object>> propertyGetter);
void ManyToMany<TLeftEntity, TRigthEntity>();
void ManyToOne<TLeftEntity, TRigthEntity>();
void OneToOne<TLeftEntity, TRigthEntity>();
void Set<TEntity>(Expression<Func<TEntity, object>> propertyGetter);
void Bag<TEntity>(Expression<Func<TEntity, object>> propertyGetter);
void List<TEntity>(Expression<Func<TEntity, object>> propertyGetter);
void Array<TEntity>(Expression<Func<TEntity, object>> propertyGetter);
void Dictionary<TEntity>(Expression<Func<TEntity, object>> propertyGetter);

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

void PersistentProperty<TEntity>(Expression<Func<TEntity, object>> propertyGetter);
}

I would read some proposal for a IDddMapper.

For example

  • instead “Component” use “ValueObject”
  • instead define the cascade behavior between two classes use AggregateRoot
  • Add …

Have you a proposal with a little explication of the expected behavior ?

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.