In the last post we saw how to put ConfORM in action, this time..before digging into what you can do with it I would like to solve a problem that you (like me) will surely have to face if you plan to use ConfORM in a pre-existing project.

Let’s consider the typical scenario: you already have NHibernate up and running, but all your mappings are actually done using the standard XML mapping technique (which I still recommend to use when dealing with legacy databases). Now you need to extend the application and you have full control over the new tables that will be added, you don’t want to loose too much time rewriting all your mappings during the development stage; ConfORM is well suited to map this portion of your domain.

The main problem is that you do not want to touch what’s already working too much (that is you do not want to rewrite all your mapping to use ConfORM along all your application, because you have to define too many exceptions), plus some of your new classes can have references to portions of the domain mapped as XML and vice-versa...the question is: can I use a mixed mapping technique ?

The answer is: YES! Thanks to NHibernate and ConfORM great flexibility. Let’s see how modifying the example I had in my previous post.

Our domain consist of the following classes mapped using ConfORM: Person, Adult and Child.

We introduce a new entity called ‘Alien’ that we will map using XML file, and we’ll define some relationship between this class and the previous ones.

Here’s how we change the domain:

public class Alien
{
	public virtual int Id { get; set; }

	public virtual string Name { get; set; }

	public virtual Person HumanFriend { get; set; }
}

public class Child : Person
{     
	public virtual Adult Father { get; set; }

	public virtual Adult Mother { get; set; }

	public virtual Alien EtFriend { get; set; }
}

As you can see a Child can now have an Alien friend (remember ET ? ) and an Alien can have a human friend.

Here’s the mapping for the Alien object:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
						 namespace="ConfORM_Tests.Domain"
						 assembly="ConfORM_Tests">
	<class name="Alien" table="Alien">
		<id name="Id">
			<generator class="native" />
		</id>
		<property name="Name" />
		<many-to-one name="HumanFriend" />
	</class>
</hibernate-mapping>

We create and initialize the NHibernate configuration object the usual way we do when it comes to XML files:

Configuration nhConfig = ConfigureNHibernate();

nhConfig.AddAssembly(typeof(Alien).Assembly);
// initialize ConfORM engine
InitializeConfORM(nhConfig);

The ‘major’ changes are done in how we define the mapping shape in ConfORM:

private static void InitializeConfORM(Configuration nhConfig)
{
	var orm = new ObjectRelationalMapper();
	var mapper = new Mapper(orm);

	// define the mapping shape

	// list all the entities we want to map.
	// let's exclude the 'Alien' which will be mapped with a standard XML file 
	IEnumerable<Type> baseEntities = typeof(Person).Assembly.GetTypes()
		.Where(t => t.Namespace == typeof(Person).Namespace && t != typeof(Alien));

	// defines the whole hierarchy coming up from Person
	orm.TablePerClassHierarchy<Person>();

	// we map all the other classes as Table per class
	orm.TablePerClass(baseEntities.Where(t => !typeof(Person).IsAssignableFrom(t)));

	// specify the relation we have between Child and Alien otherwise conform will generate a property by default.
	// it has no real clue of what an 'Alien' is and we have to help him!
	orm.ManyToOne<Child, Alien>();
	orm.ManyToOne<Alien, Person>();

	// compile the mapping for the specified entities
	HbmMapping mappingDocument = mapper.CompileMappingFor(baseEntities);

	// dump the mapping to the console
	Console.Write(mappingDocument.AsString());

	// inject the mapping in NHibernate
	nhConfig.AddDeserializedMapping(mappingDocument, "Domain");
	// fix up the schema
	SchemaMetadataUpdater.QuoteTableAndColumns(nhConfig);
}

The key points are:

  • Line 10: we need to exclude the XML mapped entities from the list of classes we want to map with ConfORM.
  • Lines 21-22: we need to specify the kind of relation that will exists between the differently mapped classes, this must be done because ConfORM is not able to discover the relationship by itself. It has no clue of what an Alien is! If we do not help him he will generate a standard ‘property’ mapping for any object it does not know.
  • Lines 25-31: we add the ConfORM generated mapping to the NHibernate configuration. How flexible!

Done!

If we look at the generated mapping for the child object we can see:

...
  <subclass name="Child" extends="Person">
    <many-to-one name="Father" />
    <many-to-one name="Mother" />
    <many-to-one name="EtFriend" />
  </subclass>
...

Without the hint on the relations we gave to ConfORM it would have generated a <property name=”EtFriend” /> entry for the mapping.

We can now use NHibernate to generate our database and write a test like this and verify that it passes:

[Test]
public void T03_ChildAlien_TheyCanBeFriends()
{
	ISessionFactory sf = CreateDatabaseAndGetSessionFactory();

	// create the relation
	using (ISession s = sf.OpenSession())
	{
		int childId;

		using (ITransaction tx = s.BeginTransaction())
		{
			Alien alien = new Alien { Name = "Xfuncz" };
			s.SaveOrUpdate(alien);

			Child child = new Child { FirstName = "Teddy", LastName = "Strange", BirthDate = DateTime.Now, EtFriend = alien };
			s.SaveOrUpdate(child);
			childId = child.Id;

			tx.Commit();
		}

		s.Clear();

		using (ITransaction tx = s.BeginTransaction())
		{
			// get the data
			var loaded = s.Get<Child>(childId);

			tx.Commit();

			Assert.IsNotNull(loaded);
			Assert.IsNotNull(loaded.EtFriend);
			Assert.AreEqual("Xfuncz", loaded.EtFriend.Name);
		}
	}
}

I’m just amazed by the flexibility of NHibernate and ConfORM.

I haven’t tested all the possible cases or relations and with very complex object hierarchies I think you can still have trouble and you need to convert part of the mappings, but nonetheless it’s a very good starting point to introduce ConfORM in your existing applications.

The following file is a complete test project you can use as a reference for the previous example:

Related Content