ConfORM – Another NHibernate mapping possibility
I recently hold two presentations at the .Net User Group Bern (DNUG) with René Leupold about object relational mapping in the .Net world. We showed Entity Framework 4.0 and NHibernate. My part was NHibernate. You could download the slides and samples from the DNUG website.
In the two presentations I showed the mapping possibilities with hbm.xml files, attributes and Fluent NHibernate. In a previous blog post I already showed those possibilities.
During the preparations I hadn’t time to try a new way to map your entities to the database. This new way is offered by the framework ConfORM, created by one of the contributors of NHibernate Fabio Maulo.
In a previous post I showed the other possibilities how you can map your entities. In this post I show you the most simplest way I found to map the entities with ConfORM. I used for this example the version 1.0.2 (Alpha 2) of ConfORM and the version 3.0.0 Alpha 2 of NHibernate. The current version of ConfORM is available here.
Domain
The domain for this sample is quite simple. It is a one to many mapping between Order and its OrderItems. The associations is bidirectional. Below you see the class diagram of the two classes:
Database
The following ERD show the two tables Order and OrderItem in the database. Between the two tables exists a foreign key constraint. None of the fields are nullable.
Configuration
First, we need to declare the connection string, so that we could connect to the database. I could do it in the code, but even for a such simple example it is too dirty for me. So I added an application configuration file (app.config) and added following lines:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <connectionStrings> <add name="default" connectionString="Server=localhost;database=ORMSamples;Integrated Security=SSPI;"/> </connectionStrings> </configuration>
Now I have a connectionstring named “default” which points to my local database ORMSamples.
When you are familiar with NHibernate, you know that you have to have a place where your session factory lives and will be created. For this purpose I normally create a class called PersistenceManager. The field factory, the property Factory and the method OpenSession are the code I would create also when I map my entities with hbm.xml files or with Fluent NHibernate or with attributes.
Where it starts to be different is the CreateConfiguration method. In this method I could create the configuration part by code. This fluent API to create a configuration is called Loquacious. Normally I would declare all this stuff in the application configuration file.
I think for a such simple application it is OK to do it like that, but I prefer the way over the application configuration file. The reason for that is, that I could configure my application for each scenario (depends on database product, etc.).
namespace ORMSamples.ConfORM.Utils { public class PersistenceManager { private static ISessionFactory factory; private static ISessionFactory Factory { get { if(factory == null) { Configuration config = CreateConfiguration(); factory = config.BuildSessionFactory(); } return factory; } } public static ISession OpenSession() { return Factory.OpenSession(); } private static Configuration CreateConfiguration() { var configure = new Configuration(); configure.SessionFactoryName("Demo"); configure.Proxy(p => p.ProxyFactoryFactory<ProxyFactoryFactory>()); configure.DataBaseIntegration(db => { db.Dialect<MsSql2008Dialect>(); db.Driver<SqlClientDriver>(); db.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote; db.ConnectionString = ConfigurationManager.ConnectionStrings["default"].ConnectionString; db.LogSqlInConsole = true; db.HqlToSqlSubstitutions = "true 1, false 0, yes 'Y', no 'N'"; }); configure.AddDeserializedMapping(GetMapping(), "ORMSamples_ConfORM"); return configure; } private static HbmMapping GetMapping() { return null; } } }
Yet I didn’t map one entity but there is an empty method GetMapping. In this method we will program our mapping in the next section.
Mapping
Below you see the completed class PersistenceManager. The implementation was created based on the example in the source code of ConfORM. The main method here is the GetMapping method. It implements a template method pattern to create the mapping.
Very important for the mapping is the method DomainDefinition. There you define your domain what means you declare all root entities. In our case it means that we have to add Order and OrderItem as root entities. For this purpose you have to call TablePerClass on the instance of the ObjectRelationalMapper class for each entity (or call it once for a collection of entities).
The patterns in ConfORM I will leave here out and come directly to the Customize method. In this method I implemented again a template method pattern. In the method CustomizeRelations I define all specialties of my entities, in the method CustomizeTables I define the DB-Schemas and finally in the method CustomizeColumns I define that the properties are all not nullable.
namespace ORMSamples.ConfORM.Utils { public class PersistenceManager { private static ISessionFactory factory; private static ISessionFactory Factory {...} public static ISession OpenSession() {...} private static Configuration CreateConfiguration() {...} private static HbmMapping GetMapping() { ObjectRelationalMapper orm = new ObjectRelationalMapper(); orm.Patterns.PoidStrategies.Add(new NativePoidPattern()); Mapper mapper = new Mapper(orm); var entities = new List<Type>(); DomainDefinition(orm); RegisterPatterns(mapper, orm); Customize(mapper); entities.AddRange(GetEntities()); return mapper.CompileMappingFor(entities); } private static void DomainDefinition(ObjectRelationalMapper orm) { orm.TablePerClass(GetEntities()); } private static void RegisterPatterns(Mapper mapper, IDomainInspector domainInspector) { } private static void Customize(Mapper mapper) { CustomizeRelations(mapper); CustomizeTables(mapper); CustomizeColumns(mapper); } private static void CustomizeRelations(Mapper mapper) { mapper.Class<Order>(cm => { cm.Id(o => o.Id, im => im.Generator(Generators.Identity)); cm.Bag( o => o.Items, x => { x.Key(k => k.Column("OrderId")); x.Lazy(CollectionLazy.NoLazy); }, x => { }); }); mapper.Class<OrderItem>(cm => { cm.Id(o => o.Id, im => im.Generator(Generators.Identity)); cm.ManyToOne( x => x.Order, m => { m.Column("OrderId"); m.Fetch(FetchMode.Join); m.NotNullable(true); }); }); } private static void CustomizeTables(Mapper mapper) { mapper.Class<Order>(cm => cm.Schema("OneToMany")); mapper.Class<OrderItem>(cm => cm.Schema("OneToMany")); } private static void CustomizeColumns(Mapper mapper) { mapper.Class<Order>( cm => { cm.Property(x => x.OrderNumber, m => m.NotNullable(true)); cm.Property(x => x.CompanyName, m => m.NotNullable(true)); cm.Property(x => x.Street, m => m.NotNullable(true)); cm.Property(x => x.PostalCode, m => m.NotNullable(true)); cm.Property(x => x.City, m => m.NotNullable(true)); }); mapper.Class<OrderItem>( cm => { cm.Property(x => x.Quantity, m => m.NotNullable(true)); cm.Property(x => x.ProductId, m => m.NotNullable(true)); }); } private static IEnumerable<Type> GetEntities() { return typeof(Order).Assembly.GetTypes().Where(t => t.Namespace == typeof(Order).Namespace); } } }
How to test it
Normally I would start with the test first, but this post is about ConfORM and not about TDD or the test first approach. To test our simple example is quite easy: You could create some CRUD-Tests. To not clutter this blog post I publish here just a Create-Test. As you can see, it’s normal NHibernate code and the whole mapping is encapsulated in the PersistenceManager class.
namespace ORMSamples.ConfORM.Test.OneToMany { [TestFixture] public class OneToManyCRUDTest { private Order order; private OrderItem orderitem; [SetUp] public void SetUp() { order = new Order(); orderitem = new OrderItem(); } [TearDown] public void TearDown() { using(ISession session = PersistenceManager.OpenSession()) using(ITransaction tx = session.BeginTransaction()) { session.Delete(order); tx.Commit(); } } [Test] public void TestCreate() { // Arrange order.OrderNumber = "12345"; order.CompanyName = "Test Company"; order.Street = "Hauptstrasse 1"; order.PostalCode = "1234"; order.City = "Zürich"; orderitem.Order = order; order.Items.Add(orderitem); orderitem.ProductId = 234; orderitem.Quantity = 1; // Act using(ISession session = PersistenceManager.OpenSession()) using(ITransaction tx = session.BeginTransaction()) { session.Save(order); tx.Commit(); } // Assert Assert.AreNotEqual(0, order.Id); Assert.AreNotEqual(0, orderitem.Id); } } }
Conclusion
The first impression of ConfORM was that it seemed to me more complicated than Fluent NHibernate. But after a while I was able to work with. I’m sure, that I didn’t understand all implemented ideas, but the framework still in alpha so I have time to learn them.
ConfORM doesn’t say you how to manage the code where you specify your mappings like Fluent NHibernate. So, you have to be cautious not to code all your mappings in one class (as I did with the PersistenceManager). In this point I find the more stricter way of Fluent NHibernate a bit better.
The owner and contributor of ConfORM, Fabio Maulo, has react very fast as I asked him to implement the missing fetch attribute for collections. If you have questions about ConfORM, you can ask him directly via twitter or the community via the Google user group. ConfORM is definitively an interesting alternative for mapping entities in a fluent way.
5 thoughts on “ConfORM – Another NHibernate mapping possibility”
This is your full mapping
private static HbmMapping GetMapping()
{
var allEntities = typeof(Order).Assembly.GetTypes().Where(t => t.Namespace == typeof(Order).Namespace);
var orm = new ObjectRelationalMapper();
orm.Patterns.PoidStrategies.Add(new IdentityPoidPattern());
orm.TablePerClass(allEntities);
var patternsAppliers = new CoolPatternsAppliersHolder(orm);
var mapper = new Mapper(orm, patternsAppliers);
mapper.SetAllPropertiesAndReferencesAsNotNullable();
mapper.Customize(cm => cm.Collection(o => o.Items, x => x.Lazy(CollectionLazy.NoLazy)));
mapper.Customize(cm => cm.ManyToOne(x => x.Order, m => m.Fetch(FetchMode.Join)));
return mapper.CompileMappingFor(allEntities);
}
public static class ConfORMMapperExtension
{
public static void SetAllPropertiesAndReferencesAsNotNullable(this Mapper mapper)
{
mapper.AddPropertyPattern(mi=> true, pm=> pm.NotNullable(true));
mapper.AddManyToOnePattern(mi=> true, mtom=> mtom.NotNullable(true));
}
}
Sorry for my late response. So here my feedback:
– Patterns for ObjectRelationalMapper class: That is where my knowledge in ConfORM isn’t yet very good -> see mention in my blog post
– CoolPatternsAppliersHolder: as above…
– Extension method for not nullable fields: Here you read the requirements very well (unfortunately not every developer do that in the way you did). But for a real world applications this way wouldn’t be useful
– Shorter GetMapping method: nice job & code!
I found your compact version very useful and understandable. The only thing, which I find a bit magic, are the patterns. But that’s always the point with “convention over configuration”. You have to put somewhere the logic. So, I have to study this pattern stuff and a blog post of you would be also a great help.
Thank you again for your feedback and I hope I will have the chance again the pleasure in future.