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);
}