DCSIMG
Another NHibernate 101? - Technicals and Technicalities

Technicals and Technicalities

Ariel's uneditable Bliki

Another NHibernate 101?

Yesterday at the Alt.Net tools night I gave a short talk about how to get started with NHibernate. “Getting started” means we’re talking about Greenfield project, heavily favoring Conventions (and automappings) over Configuration, and not having to mess with existing, untouchable codebase or DB schema.

The weird part was sitting there, after having used NH, a 5 year-old project, for about a week, and across the table sat Ayende, one of the main contributors to NH (and author of NHProf). Most of the time he wasn’t throwing heavy items at me, so I called that session a success.

 

The goal of the demo was to make this integration test pass:

[Test]
public void SaveAndLoadEntity_AssertSavedValueIsSame()
{
    // Arrange
    var instance = new Saver();
    var entity = new MyEntity();
    entity.Val = 34;
    instance.Save(entity);
 
    // Act
    var retVal = instance.Load();
 
    // Assert
    Assert.AreEqual(34, retVal.Val);
}

(The test was created in exactly three seconds using QuickUnit. Try it out.)

I won’t re-run all the “run test, fix error” iterations here, just review the concepts and show the outcome.

DB config Issues:

  • We want the test to pass as easily as possible, have no side effects, and require minimal setup costs – so we’ve used SQLite in-memory DB, using System.Data.Sqlite (free). The single DLL is the ADO.NET adapter AND the actual DB.
  • Standard configuration of in-memory SQLite kills the DB after every session closes, so we needed a different connection string.
  • Since we’re creating a new DB every test – we need to create the DB schema every time, using the SchemaExport tool. (More)
  • Make sure the reference to System.Data.Sqlite has the “Copy local = true” flag (as it is not a completely managed dll, and does not default to true).
  • Also watch out for 64bit issues and FW4.0 issues with System.Data.Sqlite.

Mapping issues:

  • We desperately want to use FluentNHibernates’ automapping, so we provide an assembly to scan for entities, and a minimal adaptation of the DefaultAutomappingConfiguration object (I’ve chose to use attributes to mark persisted types. Inheritance can do just the same).
  • Every entity needs a primary key in RDBMS. So we’ve added the Id property on MyEntity.
  • NH wants to provide you with lazy loading for your data, so it needs to override your properties (creating dynamic proxies at runtime), so you need to make them virtual (and add a reference to the NHibernate.ByteCode.Castle assembly)

NHibernate usage issues:

  • An ISession object is lightweight (and can be thought of as “Cache Scope”). Create one when it’s a logical thing to do.
  • An ISessionFactory object is VERY heavyweight. Create and save it.
  • Always open (and commit) a transaction (saving OR loading).
  • The three main query mechanisms for NH are HQL, ICriteria and Linq2NH. I find it silly that in 2010 I won’t use linq to express my queries, so (for NH 2.1.2 only) you need to add the NHibernate.Linq.dll. NH v3.0 incorporated that syntax in the core.

Test issues:

  • Make sure you add the required references above to the tests project, so that NHibernate can, at runtime, use all those assemblies required.
   1: using System;
   2: using FluentNHibernate.Automapping;
   3: using FluentNHibernate.Cfg;
   4: using FluentNHibernate.Cfg.Db;
   5: using NHibernate;
   6: using System.Linq;
   7: using NHibernate.Cfg;
   8: using NHibernate.Linq;
   9: using NHibernate.Tool.hbm2ddl;
  10:  
  11: namespace NHStart
  12: {
  13:     public class Saver
  14:     {
  15:         public void Save(MyEntity entity)
  16:         {
  17:             using (var session = GetSession())
  18:             using(var tx = session.BeginTransaction())
  19:             {
  20:                 session.Save(entity);
  21:  
  22:                 tx.Commit();
  23:             }
  24:         }
  25:  
  26:         private ISession GetSession()
  27:         {
  28:             var sessionFactory = Factory;
  29:             return sessionFactory.OpenSession();
  30:         }
  31:  
  32:         protected ISessionFactory _Factory;
  33:         protected ISessionFactory Factory
  34:         {
  35:             get
  36:             {
  37:                 if (_Factory == null)
  38:                 {
  39:                     Configuration config = null;
  40:  
  41:                     _Factory =
  42:                         Fluently
  43:                             .Configure()
  44:                             .Database(SQLiteConfiguration.Standard.ConnectionString(x => x.Is("Data Source=:memory:;Version=3;New=True;Pooling=True;Max Pool Size=1")))
  45:                             .Mappings(x=>x.AutoMappings.Add(AutoMap
  46:                             .Assemblies(new MyAutomappingConfiguration(), typeof(MyEntity).Assembly)))
  47:                             .ExposeConfiguration(x=>config = x)
  48:                             .BuildSessionFactory();
  49:  
  50:                     using (var session = _Factory.OpenSession())
  51:                     {
  52:                         new SchemaExport(config).Execute(false, true, false, session.Connection, Console.Out);
  53:                     }
  54:  
  55:                 }
  56:                 return _Factory;
  57:             }
  58:         }
  59:  
  60:         public MyEntity Load()
  61:         {
  62:             using (var session = GetSession())
  63:             using (var tx = session.BeginTransaction())
  64:             {
  65:                 var item = session
  66:                     .Linq<MyEntity>()
  67:                     .First();
  68:  
  69:                 tx.Commit();
  70:  
  71:                 return item;
  72:             }
  73:         }
  74:     }
  75:  
  76:     public class MyAutomappingConfiguration : DefaultAutomappingConfiguration
  77:     {
  78:         public override bool ShouldMap(Type type)
  79:         {
  80:             return type.GetCustomAttributes(true).OfType<PersistedAttribute>().Any();
  81:         }
  82:     }
  83:  
  84:     [Persisted]
  85:     public class MyEntity
  86:     {
  87:         public virtual int Id { get; set; }
  88:         public virtual int Val { get; set; }
  89:         public virtual string Val2 { get; set; }
  90:     }
  91:  
  92:     public class PersistedAttribute : Attribute {}
  93: }

I’ve even added line count to show how 93 lines of code can create a (simple) persistence layer. It was fun.

Comments

ripper234 said:

Great post. I wasn't able to attend this talk, but I really like the idea.

Too many tools and frameworks out there don't have an easy "Hello World" guide, and are relatively hard to learn for the first time. Good work on providing one.

# December 19, 2010 1:54 PM

Codebix.com said:

This post has been featured on Codebix.com. The place to find latest articles on programming. Click on the url to reach your post's page.

# December 20, 2010 11:05 AM

Ariel said:

Thanks, Ron. That was the motivation for the talk, too. :)

# December 20, 2010 2:14 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: