Introduction to NHibernate, pt. 7

In earlier parts of this series we looked at mapping via XML mapping files. I wanted to look at some alternatives to the angle bracket tax. First up is the Castle project’s Active Record. Next time we will look at Fluent NHibernate.

Active Record Support for Attribute Based Mapping

While mapping via an xml file allows access to the full range of options that NHibernate gives us, it can be slow, because it forces us to context switch between C# and XML. Both NHibernate and Castle have attribute mapping options. NNHibernate.Mapping.Attributes is an add-in for NHibernate contributed by Pierre Henri Kuaté. Active Record is part of the Castle project, originally intended to support the Active Record pattern favored by Ruby On Rails.

We’ll just discuss how to use Active Record’s Attribute Support here. Essentially ActiveRecord contains attributes that are the equivalent of elements from the mapping file. For example to map the Customer class we mark it up as follows.

Adding the required references

We need to reference a number of assemblies to get ActiveRecord support:
Castle.ActiveRecord.dll
Castle.Core.dll
Castle.Components.Validator.dll
Castle.DynamicProxy.dll
NHibernate.dll

We also need to change our configuration file to configure ActiveRecord instead of NHibernate (ActiveRecord configures NHibernate from this information). We an configure either by a stand-alone xml file or through the app.config. For simplicity we configure through the app.config file here.

We need to add a config section handler for the active record section of our configuration file:

    <configSections>
        <section name=”activerecord” type=”Castle.ActiveRecord.Framework.Config.ActiveRecordSectionHandler, Castle.ActiveRecord” />
    </configSections>

The configuration mainly tells NHibernate how to access the Db. There are two key attributes of the <activerecord> element. We need to set the IsWeb attribute to true if we are an asp.net web application. We need to set the IsDebug attribute to true, if we want to see the generated mapping files (see below).

Note that when working with NHibernate 2.0+ the sample code given on the Castle site for Active Record uses incorrect key values that are prefixed with NHibernate. You need to change these to omit the “hibernate.” from the name. If you forget to do this you will get messages about invalid key from log4net. This is because the hibernate.* keys are no longer in the schema.

<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
  <configSections>
    <section name=”activerecord” type=”Castle.ActiveRecord.Framework.Config.ActiveRecordSectionHandler, Castle.ActiveRecord” />
    <section name=”log4net” type=”log4net.Config.Log4NetConfigurationSectionHandler, log4net” />
  </configSections>

  <activerecord isWeb=”false” isDebug=”true”>
    <config>
      <add
          key=”connection.driver_class”
          value=”NHibernate.Driver.SqlClientDriver” />
      <add
          key=”dialect”
          value=”NHibernate.Dialect.MsSql2000Dialect” />
      <add
          key=”connection.provider”
          value=”NHibernate.Connection.DriverConnectionProvider” />
      <add
          key=”connection.connection_string”
          value=”Data Source=.;Initial Catalog=nhibernate;Integrated Security=SSPI” />
    </config>
  </activerecord>
 
  <log4net>
    <appender name=”ConsoleAppender” type=”log4net.Appender.ConsoleAppender, log4net”>
      <layout type=”log4net.Layout.PatternLayout, log4net”>
        <param name=”ConversionPattern” value=”%m” />
      </layout>
    </appender>
    <root>
      <priority value=”DEBUG” />
      <appender-ref ref=”ConsoleAppender” />
    </root>
  </log4net>
 
</configuration>

Using attributes to peform our mapping

We mark the entity with an [ActiveRecord] attribute to indicate that we want NHibernate to persist it. We can add the table name if it does not match the entity name [ActiveRecord("MyTableName")]. We can indicate that we want the entity to be loaded, by adding the Lazy attribute as in [ActiveRecord(Lazy=true)].

We can declare that our class inherits from ActiveRecordBase if we want Active Record pattern semantics, i.e. the entity knows how to retrieve and persist itself. We want true seperation of concerns, so we do not intend to mix CRUD mechanics with our entity. For this reason we do not derive our class from ActiveRecordBase.

We mark up the identity of our entity using the [PrimaryKey] attribute. The default primary key type is native, so we do not need to mark this up, but if we have a different strategy then we have to explicitly indicate that. For example to use a GUID we would use: PrimaryKey(PrimaryKeyType.UuidHex, Params=”format=D,seperator=-”)]

We can mark up a property using the [Property] attribute. In addition, we can use [Field] if we want to map a private member of the class. If we need to map to a column with a different name to the property then we can pass it as a parameter to the attribute [Property("MyColumnName")]. If we have a large object then we can set the column type – see the the Active Record documentation.

We use a [HasMany] attribute to represent a one-to-many relationship, attributing the collection on the parent class i.e. [HasMany(typeof(Post), Table="Posts", ColumnKey="blogid")]. We use the RelationType enumerated value to specify the type of the relationship i.e. RelationshipType.Set. We also set the Inverse parameter as part of the HasMany attribute if this is a bi-directional relationship to avoid generating the insert and update statement, and creating an insert only. We can indicate if this relationship should be lazy-loaded using the Lazy parameter on the HasMany attribute. As with XML mapping we can also set the action NHibernate should take when we update or delete the parent using the Cascade option. In addition, we can set the index if we use one of the ordered types for our relationship or the sort order.
We use a [BelongsTo] attribute on the reverse side of the relationship to indicate a Many-To-One relationship. We need to pass the name of the foreign key column as a parameter to the attribute. Again there are options that reflect those available from the xml mapping file.

ActiveRecord also provides support for more advanced scenarios that are outside the scope of this article (it is meant to be an introduction and most of this is documented elsewhere):

HasAndBelongsToMany
OneToOne
Any and HasManyToAny

ActiveRecord also supports inheritance, supporting both the table Table-per-class hierarchy and Table-per-subclass strategies.

Mapping the Table-per-class hierachy strategy is straightforward, just set the parameters on the superclass ActiveRecord attribute to indicate the discriminator column to use i.e. [ActiveRecord("companies", DiscriminatorColumn="type", DiscriminatorType="String", DiscriminatorValue="company")] and then indicate the value for each of the sub-classes i.e. [ActiveRecord(DiscriminatorValue="firm")].
Mapping the Table-per-subclass strategy requires us to use the JoinedBase attribute on the superclass ActiveRecord attribute i.e. [ActiveRecord("entity"), JoinedBase] and then use the [JoinedKey] attribute on the shared key value of the sub-classes.

ActiveRecord also provides support for fine grained object models. We declare the [Nested] attribute on the property declaration in the containing entity, and then only mark up the component class with [Property] attributes i.e. not with [ActiveRecord] or [PrimaryKey] attributes.

Configuring Active Record

Before using ActiveRecord we must invoke the method Initialize on ActiveRecordStarter to configure NHibernate for us.There are a range of options to use when configuring, depending on where your configuration information is found. Because we added our configuration to the app.config file we can use the ActiveRecordSectionHandler to retrieve our configuration information.
ActiveRecordStarter also provides the functionality to generate our schema from our code.

            log4net.Config.XmlConfigurator.Configure();
            IConfigurationSource config = ActiveRecordSectionHandler.Instance;
            ActiveRecordStarter.Initialize(typeof(Company).Assembly, config);
            ActiveRecordStarter.CreateSchema();

Switching from attributes to mapping files

Active Record generates XML mapping files at run-time from your attributes, so this is using the same mechanism as the XML configuration, the complexity is just hidden from you. If you want to see the mapping then you can set the Debug value to true on the active record configuration (see above). This will tell NHibernate to keep the files around when your application ends, instead of deleting them.
This allows you to pursue a strategy of beginning development by using attributes, and then switching to .hbm.xml files later to fully utilize all options and keep your domain model clean of mapping information.

Active Record support for Session Management

Active Record removes the need to initialize a session factory explicitly, instead we use the ActiveRecordStarter.

            IConfigurationSource config = ActiveRecordSectionHandler.Instance;
            ActiveRecordStarter.Initialize(typeof(Company).Assembly, config);

For a web based application best practice is that we initialize within the Application_Start method of the global HttpApplication object.

Active Record operations try to get a session from the current thread, but create one if it is not extant, perform the operation and then release the session. So by default Active Record is session per operation. Active Record also gives us the ability to use a SessionScope to keep the session alive for the scope of our business transaction. Best practice is always to use a SessionScope.

            using (new SessionScope())
           {
                const string COMPANY_NAME = “CompanyOfWolves”;
                InsertNewCompany(COMPANY_NAME);
                int companyId = FindInsertedCompany(COMPANY_NAME);
            }

For a web application the usual pattern is to create a SessionScope with request affinity

 

Active Record and Repositories

The problem with the Active Record pattern, where the entity exposes CRUD methods is that it does not enforce a clean separation of concerns. (We have not shown this but to get Active Record working this way we need our entities to derive from ActiveRecordBase). Knowledge about persistence is coupled with knowledge about domain responsibilities. This increases complexity and thus makes maintenance harder. So good design strives for persistence ignorance within its domain model. The repository lives within the domain model and represents a collection of entities from an aggregate root.

We call the repository from our domain level service to persist the object graph.

 

Support for Repository within Active Record

Active Record provides ActiveRecordMediator to allow you to use ActiveRecord to implement a repository.

   public class CompanyRepository
    {
        protected ActiveRecordMediator<Company> mediator;

        public void Add(Company item)
        {
            ActiveRecordMediator<Company>.Save(item);
        }
        public virtual this[ int id]
        {

            get { return ActiveRecordMediator<Company>.FindByPrimaryKey(id); }
        }
        public Company[] Find(IActiveRecordQuery query)
        {
            return (Company[])ActiveRecordMediator<Company>.ExecuteQuery(query);
        }

        public Company[] Items()
        {
            return ActiveRecordMediator<Company>.FindAll();
        }

        public virtual int Count()
        {
            return ActiveRecordMediator<Company>.Count();
        }

    }

A quick example of using this repository (domain services etc. elided to remove complexity) would be:

     public static void Main(string[] args)
        {
            log4net.Config.XmlConfigurator.Configure();

            IConfigurationSource config = ActiveRecordSectionHandler.Instance;

            ActiveRecordStarter.Initialize(typeof(Company).Assembly, config);

            ActiveRecordStarter.CreateSchema();

            //Comment out the session scope to see lazy load lack of scope issues
            using (new SessionScope())
            {
                const string COMPANY_NAME = “CompanyOfWolves”;
                InsertNewCompany(COMPANY_NAME);
                int companyId = FindInsertedCompany(COMPANY_NAME);
            }
        }

        private static int FindInsertedCompany(string companyName)
        {
            IActiveRecordQuery query = new SimpleQuery<Company>(“from Company org where Name = ?”, companyName);
           
            CompanyRepository companyRepository = new CompanyRepository();
            Company[] companies = companyRepository.Find(query);
            Company company = companies[0];
            Console.Out.WriteLine(company.Name);

            foreach (Customer customer in company.Customers)
            {
                Console.Out.WriteLine();
                Console.Out.Write(“First Name: “);
                Console.Out.WriteLine(customer.Name.FirstName);
                Console.Out.Write(“Surname: “);
                Console.Out.WriteLine(customer.Name.Surname);
            }
            return company.Id;
        }

        private static void InsertNewCompany(string companyName)
        {
            Company company = new Company();
            company.Name = companyName;

            Customer customer = new Customer();
            customer.Name.FirstName = “Neil”;
            customer.Name.Surname = “Jordan”;
            customer.DateOfBirth = new DateTime(1950, 2, 25);
            customer.Company = company;

            company.Customers.Add(customer);

            CompanyRepository companyRepository = new CompanyRepository();
            companyRepository.Add(company);

        }

About Ian Cooper

Ian Cooper has over 18 years of experience delivering Microsoft platform solutions in government, healthcare, and finance. During that time he has worked for the DTi, Reuters, Sungard, Misys and Beazley delivering everything from bespoke enterpise solutions to 'shrink-wrapped' products to thousands of customers. Ian is a passionate exponent of the benefits of OO and Agile. He is test-infected and contagious. When he is not writing C# code he is also the and founder of the London .NET user group. http://www.dnug.org.uk
This entry was posted in .Net, Castle, DDD, NHibernate. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • civil engineers

    I was thinking of looking up some of them newspaper websites, but am glad I came here instead. Although glad is not quite the right word… let me just say I needed this after the incessant chatter in the media, and am grateful to you for articulating something many of us are feeling – even from distant shores. Feel free to check out my site structural engineers when you got time.

  • recycling containers

    You share valuable information and excellent design you got here! I would like to thank you for sharing your thoughts and time into the stuff you post!! Thumbs up. Feel free to check out my site glass recycling when you got time.

  • http://www.roofingcontractorbuilder.com roofing contractors

    I enjoyed reading your work! GREAT post! I looked around for this… but I found you! Anyway, would you mind if I threw up a backlink from my site? Please come visit my site roofing supply and give me any valuable feedbacks.

  • http://www.serviceroadside.com towing

    I enjoyed reading your work! GREAT post! I looked around for this… but I found you! Anyway, would you mind if I threw up a backlink from my site? Please come visit my site paving and give me any valuable feedbacks.

  • http://www.resalestorelist.com discount stores

    I usually don’t leave comments!!! Trust me! But I liked your blog…especially this post! Would you mind terribly if I put up a backlink from my site to your site? Please come visit my site discount storediscount store

  • http://www.resalestorelist.com discount stores

    I usually don’t leave comments!!! Trust me! But I liked your blog…especially this post! Would you mind terribly if I put up a backlink from my site to your site? Please come visit my site discount store when you got time

  • http://www.resalestorelist.com discount stores

    I usually don’t leave comments!!! Trust me! But I liked your blog…especially this post! Would you mind terribly if I put up a backlink from my site to your site? Please come visit my site discount store when you got time

  • http://www.anaheimcaguide.com Anaheim Business Directory

    I am not really sure if best practices have emerged around things like that, but I am sure that your great job is clearly identifed. I was wondering if you offer any subscription to your RSS feeds as I would be very interested and can’t find any link to subscribe here. Please come visit my site Anaheim Yellow Page Business Directory when you got time.

  • http://www.Businesssantaana.com Santa Ana Business Directory

    You may have not intended to do so, but I think you have managed to express the state of mind that a lot of people are in. The sense of wanting to help, but not knowing how or where, is something a lot of us are going through. Please come visit my site Santa Ana Yellow Page Business Directory when you got time.

  • http://www.michigandetroit.net Detroit Business Directory

    You got a really useful blog I have been here reading for about an hour. I am a newbee and your success is very much an inspiration for me. Please come visit my site Local Business Directory Of Detroit U.S.A. when you got time.

  • http://www.californiasanjose.info San Jose Business Directory

    I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me. Thanks for all your help and wishing you all the success in your business. Please come visit my site Local Business Directory Of San Jose U.S.A. when you got time.

  • http://jack-fx.com jack

    NHibernate, very powerful. but i prefer subsonic, a light weight O/R Mapping

  • http://geekswithblogs.net/chrisfalter Chris Falter

    Nice post, Ian! Some questions:

    1. Where does one get the Castle ActiveRecord stuff? I’d like to look at (and possibly use) it.

    2. Why does the CompanyRepository class have a protected member ActiveRecordMediator mediator that it does not use?

    3. For that matter, why couldn’t you create a generic repository class that facilitates the creation of repositories with a single line of code?

    public abstract class Repository: ActiveRecordMediator
    {
    public void Add(T item)
    {
    ActiveRecordMediator
    .Save(item);
    }
    public T[] Find(IActiveRecordQuery query)
    {
    return (T[])ActiveRecordMediator
    .ExecuteQuery(query);
    }
    // etc.
    }

    Since I don’t have the source code in front of me (see question #1), I’m not sure it this would work. And we’d probably have to clarify the semantics of instantiation (see question #2). But if this is possible, then creating a repository would take a single line of code:

    public class CompanyRepository: Repository {}

    Although maybe it would need to override a constructor… What do you think?

  • Charl

    Very nice post. Thanks very much.