Try fast search NHibernate

12 November 2008

Mapping Source: How map a class without use XML

I don’t know how many times you heard a mad man talking about “mapping source” in NHibernate…

Map a class in NH without use XML at all ? only a crazy man can say that.

Hello! I’m that mad man.

As usual an entity implementation:

public class Animal
{
public virtual int Id { get; private set; }
public virtual string Description { get; set; }
}

Is a simple class because map something else, and begin a new framework, is not the target of this post.

Now an empty method, to write an integration test, of a new class inherited from the NHibernate configuration class:

public class Configuration : NHibernate.Cfg.Configuration
{
public void Register(Type entity){}
}

The integration test, basically, include the SchemaExport, a CRUD and an HQL; what is interesting here is only the setup of the test:

protected Configuration cfg;
protected ISessionFactoryImplementor sessions;

[TestFixtureSetUp]
public void TestFixtureSetUp()
{
cfg = new Configuration();
cfg.Configure();
cfg.Register(typeof(Animal));
new SchemaExport(cfg).Create(false, true);
sessions = (ISessionFactoryImplementor)cfg.BuildSessionFactory();
}

As you can see is similar to a common setup except for cfg.Register(typeof(Animal)). The others parts of the test are available from the download link.

Now I can start the dance…

In NHibernate all classes metadata are completely decoupled from XML; this mean that SchemaExport, and everything else in NH, absolutely don’t need XML files to be used. What I must do is inject everything after call the method cfg.Configure(). The place where all metadata are available is the namespace NHibernate.Mapping. The holder of metadata is the class Configuration trough the class NHibernate.Cfg.Mappings. The provider of an instance of NHibernate.Cfg.Mappings is the Configuration itself trough the method:

/// <summary>
///
Create a new <see cref="Mappings" /> to add classes and collection
/// mappings to.
/// </summary>
public Mappings CreateMappings(Dialect.Dialect dialect)

That method stay there from loooong time ago.

As we are doing in NH-Core each “binder” must use at least two classes (to create new metadata):

  1. an instance of NHibernate.Cfg.Mappings
  2. the instance of the configured Dialect

The configuration extension

public class Configuration : NHibernate.Cfg.Configuration
{
public void Register(Type entity)
{
Dialect dialect = Dialect.GetDialect(Properties);
Mappings mappings = CreateMappings(dialect);
SetDefaultMappingsProperties(mappings);
new EntityMapper(mappings, dialect).Bind(entity);
}

private static void SetDefaultMappingsProperties(Mappings mappings)
{
mappings.SchemaName = null;
mappings.DefaultCascade = "none";
mappings.DefaultAccess = "property";
mappings.DefaultLazy = true;
mappings.IsAutoImport = true;
mappings.DefaultNamespace = null;
mappings.DefaultAssembly = null;
}
}

For each class, I’m going to register, I’m getting the configured dialect and a new instance of Mappings class. Then I’m setting some default values and at the end I’m biding the entity type (EntityMapper(mappings, dialect).Bind(entity)).

The EntityMapper

Without boring you, with the full code, the heart is here

public void Bind(Type entity)
{
var rootClass = new RootClass();
BindClass(entity, rootClass);
}

private void BindClass(Type entity, PersistentClass pclass)
{
pclass.IsLazy = true;
pclass.EntityName = entity.FullName;
pclass.ClassName = entity.AssemblyQualifiedName;
pclass.ProxyInterfaceName = entity.AssemblyQualifiedName;
string tableName = GetClassTableName(pclass);
Table table = mappings.AddTable(null, null, tableName, null, pclass.IsAbstract.GetValueOrDefault());
((ITableOwner) pclass).Table = table;
pclass.IsMutable = true;
PropertyInfo[] propInfos = entity.GetProperties();

PropertyInfo toExclude = new IdBinder(this, propInfos).Bind(pclass, table);

pclass.CreatePrimaryKey(dialect);
BindProperties(pclass, propInfos.Where(x => x != toExclude));
mappings.AddClass(pclass);

string qualifiedName = pclass.MappedClass == null ? pclass.EntityName : pclass.MappedClass.AssemblyQualifiedName;
mappings.AddImport(qualifiedName, pclass.EntityName);
if (mappings.IsAutoImport && pclass.EntityName.IndexOf('.') > 0)
{
mappings.AddImport(qualifiedName, StringHelper.Unqualify(pclass.EntityName));
}
}

Including everything the EntityMapper.cs have 198 lines.

Metadata classes used are: RootClass, PersistentClass, Table, SimpleValue, Property and Column.

Conclusions

To use NHibernate without write a single XML mapping, is possible. Create mapped classes or others artifacts (as typedef, database-objects, named-queries, stored-procedures, filters and so on) without use XML, is possible. Because I’m extending the NHibernate.Cfg.Configuration, add some other artifacts or override mapping written using XML is possible. Write a “bridge” using EntityFramework attributes instead XMLs, is possible.

Write a new framework avoiding XML at all, is possible that is completely different than “is easy”. In general a framework is to make something easy to the framework users and not to the framework developers.

Code available here.


Technorati Tags: ,

3 comments:

  1. This is a nice way of doing, especially when you have a default system-environment like a single RDBMS.

    Maybe I'll think about using such a self-developed 'small' framework.

    Thanks for this remark.

    ReplyDelete
  2. And what about Fluent Hibernate??

    ReplyDelete
  3. FNH is creating XML (btw have a look to the date of this post)

    ReplyDelete