Hands on ConfORM: our first pattern appliers (how to customize tables and properties names)

Print Content | More

In the previous articles of this series we saw how to initialize the ConfORM engine and how we can use it in an already existing project that make use of XML files to map the domain.

This time I would like to show you how you can customize some aspects of the generated mappings. One of the first things you will always need is to define your own patterns to give names to tables and fields so they can respect your conventions; I will use this scenario as a basic example to show you how flexible ConfORM is when it comes to define the shape of your mappings.

ConfORM works by discovering patterns through you domain model and applying customizations to what it’s able to identify, let’s take a look at the interface involved in the process:

IxxxMapper (IClassAttributesMapper, IPropertyMapper, etc...): ConfORM exposes a series of objects used to define each and every aspect of the mapping, they are used to set tables and column names, types, precision, etc...You have a mapper for almost everything from classes, id fields, types of relationships between entities...they are simply too many to be listed here, so take a look at the source code.

public interface IPattern<TSubject>
{
	bool Match(TSubject subject);
}

IPattern<TSubject>: this interface defines a pattern you want to implement; TSubject is the type of data (usually coming from reflection) you are analyzing to discover if the pattern you are going to implement can be applied to the type. This interface has a single method - Match() - that returns a boolean stating if the pattern can be applied to this type of object.

public interface IPatternApplier<TSubject, TApplyTo> : IPattern<TSubject>
{
	void Apply(TSubject subject, TApplyTo applyTo);
}

IPatternApplier<TSubject, TApplyTo>: this the interface your patterns have to implement in order to be usable by ConfORM. TSubject is the same as before; TApplyTo is one of the available mappers exposed by ConfORM (IClassAttributesMapper, IPropertyMapper and so on...), we talked about them before. It has a single Apply() function member you will use to invoke the methods of the mapper in order to set the mapping properties to the desired values.

Understanding the usage of this interface is vital to be able to buildup our patterns: it defines a generic pattern that can be applied to elements of type TSubject and that will act on the mappings defined by TApplyTo.

What we need to do to define our naming patterns is to implement IPatternApplier<T1,T2> and add the custom logic to define the tables and column names; let’s see two very simple examples:

public class ClassNamingApplier : IPatternApplier<Type, IClassAttributesMapper>
{
	#region IPattern<Type> Members

	public bool Match(Type subject)
	{
		return subject != null;
	}

	#endregion

	#region IPatternApplier<Type,IClassAttributesMapper> Members

	public void Apply(Type subject, IClassAttributesMapper applyTo)
	{
		applyTo.Table("Test" + subject.Name);
	}

	#endregion
}

The ClassNamingApplier is used to define a custom naming strategy for our database tables. TSubject in this case is the Type that represent the domain entity we are currently mapping, TApplyTo is the mapper object (IClassAttributesMapper) used to define the attributes of a <class> mapping; so this class represents a pattern that will act on entities and that will modify the attributes of the generated <class> mappings.

In line 07 we just state that this pattern can be applied to any object (you can have way more complex matching logic here).

In line 16 we set the custom name of the database table that will be mapped to this entity data.

To change the names of the properties we need to implement another pattern applier:

public class PropertyNamingApplier : IPatternApplier<MemberInfo, IPropertyMapper>
{

	#region IPatternApplier<MemberInfo,IPropertyMapper> Members

	public void Apply(MemberInfo subject, IPropertyMapper applyTo)
	{
		applyTo.Column("Test" + subject.Name);
	}

	#endregion

	#region IPattern<MemberInfo> Members

	public bool Match(MemberInfo subject)
	{
		return (subject != null);
	}

	#endregion
}

PropertyNamingApplier defines a pattern that will act on class’ members and will modify the mapped properties: TSubject is a MemberInfo object that describes the property under examination, TApplyTo is of type IPropertyMapper; this object will then be used to define a pattern that can be applied to the properties of a domain entity.

To fully define your custom naming strategy you need to implement several type of pattern appliers, in this blog post I just show you how you can define two of them, in the attached solution you can see some other examples. However to have a complete overview of the appliers you can write I encourage you to check the source code of ConfORM.

The last step needed to have everything work is to inform ConfORM of the existence of these new pattern:

var orm = new ObjectRelationalMapper();

// set the Persistence Object ID strategy
var patternsAppliers = new SafePropertyAccessorPack();
// add a custom naming strategy to alter the Table names
patternsAppliers.Merge(new ClassNamingApplier());
// add a custom naming strategy to alter the Id columns names
patternsAppliers.Merge(new IdNamingApplier());
// add a custom naming strategy to alter the Property column names
patternsAppliers.Merge(new PropertyNamingApplier());
// add a custom naming strategy for the relationships
patternsAppliers.Merge(new ManyToOneColumnNamingApplier());
patternsAppliers.Merge(new OneToManyKeyColumnNamingApplier(orm));

var mapper = new Mapper(orm, patternsAppliers);

Here I used a predefined pattern pack and added to it some of my custom pattern appliers, the resulting mapping is reported below:

<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="ConfORM_Tests.Domain" assembly="ConfORM_Tests" xmlns="urn:nhibernate-mapping-2.2">
  <class name="Person" table="TestPerson">
    <id name="Id" column="TestId" type="Int32">
      <generator class="hilo" />
    </id>
    <discriminator />
    <property name="FirstName" column="TestFirstName" />
    <property name="LastName" column="TestLastName" />
    <bag name="Pets">
      <key column="TestOWNER_ID" />
      <one-to-many class="Pet" />
    </bag>
    <property name="Long" column="TestLong" />
    <property name="Guid" column="TestGuid" />
    <list name="HeldItems">
      <key column="TestPERSON_ID" />
      <list-index />
      <one-to-many class="Item" />
    </list>
    <many-to-one name="RightHand" column="TestRightHandId" />
    <many-to-one name="LeftHand" column="TestLeftHandId" />
    <property name="BirthDate" column="TestBirthDate" />
  </class>
  <class name="EntityGuid" table="TestEntityGuid">
    <id name="Id" column="TestId" type="Guid">
      <generator class="guid.comb" />
    </id>
    <property name="Name" column="TestName" />
  </class>
  <class name="Item" table="TestItem">
    <id name="Id" column="TestId" type="Int32">
      <generator class="hilo" />
    </id>
    <property name="Name" column="TestName" />
  </class>
  ...

As you can notice we have added the “Test” prefix to every object.

With some very simple classes we were able to implement out custom naming strategy that is ‘spammed’ along all our domain classes; if you need to fine tune the naming generation just consider you have full control and all the information about any object involved in the mapping process: you can, for example, give a naming convention to the classes that are in a namespace and a different one to others...and so on.

Sample project:



NHibernate, ConfORM, Pattern

1 comments

  1. #1 da Fabio Maulo - Saturday October 2010 alle 02:48

    Note : there are cases where is better to write an applier based on a PropertyPath instead a MemberInfo and, btw, a pattern over a PropertyPath always can override an applier over a MemberInfo... ;)

    Nice post and thanks to contribute with your analisis and work.

All fields are required and you must provide valid data in order to be able to comment on this post.


(will not be published)
(es: http://www.mysite.com)