Advanced ActiveRecord Models – Part 1

Before you can do anything exciting, it is important to understand what exactly Castle ActiveRecord does when starting up. Specifically, what happens when you can ActiveRecordStarter#Initialize():

 

  1. Inspects supplied types or assemblies looking for ActiveRecord attributes.
  2. Creates an in memory model of the attributes (Castle.ActiveRecord.Framework.Internal.ActiveRecordModelBuilder)
  3. Uses the visitor pattern to connect the model for things like many-to-many relationships (Castle.ActiveRecord.Framework.Internal.GraphConnectorVisitor)
  4. Verifies that the model is valid (Castle.ActiveRecord.Framework.Internal.SemanticVerifierVisitor)
  5. Generates NHibernate XML mappings for the model (Castle.ActiveRecord.Framework.Internal.XmlGenerationVisitor)
  6. Initializes NHibernate with the generated mappings

 

ActiveRecordStarter provides a few events that you can attach to modify the model, ModelsCreated and ModelsValidated (I originally found out about this extension point when reviewing the code for Ayende’s Rhino Security).  What are some of the things you can do with these events?  The project I am working on uses SQL Server schemas to separate different groups of tables. So all of our table mappings look like: [ActiveRecord("`TableName`", "`SchemaName`", Lazy = True)].   We have a number of unit tests for our database classes that we want to run with SQLite so they can run on our build server with no external dependencies. SQLite does not support schemas though, so we can use ActiveRecordStarter to modify our model:

 

   1: AppSettingsSection nhibernateSection = ConfigurationManager.GetSection("nhibernate") as AppSettingsSection;
   2: if (nhibernateSection != null && nhibernateSection.Settings["dialect"].Equals("NHibernate.Dialect.SQLiteDialect"))
   3: {
   4:     ModelsDelegate validated = null;
   5:     validated = delegate(ActiveRecordModelCollection models, IConfigurationSource source)
   6:                     {
   7:                         ActiveRecordStarter.ModelsValidated -= validated;
   8:                         foreach (ActiveRecordModel model in models)
   9:                         {
  10:                             model.Accept(new ChangeSchemaVisitor());
  11:                         }
  12:                     };
  13:     ActiveRecordStarter.ModelsValidated += validated;
  14: }
  15: ActiveRecordStarter.Initialize();
  16:  
  17: ...
  18:  
  19: internal class ChangeSchemaVisitor : AbstractDepthFirstVisitor
  20: {
  21:     public override void VisitModel(ActiveRecordModel model)
  22:     {
  23:         var schema = model.ActiveRecordAtt.Schema;
  24:         model.ActiveRecordAtt.Table = schema + "_" + model.ActiveRecordAtt.Table;
  25:         model.ActiveRecordAtt.Schema = null;
  26:         base.VisitModel(model);
  27:     }
  28:  
  29:     public override void VisitHasAndBelongsToMany(HasAndBelongsToManyModel model)
  30:     {
  31:         var schema = model.HasManyAtt.Schema;
  32:         model.HasManyAtt.Table = schema + "_" + model.HasManyAtt.Table;
  33:         model.HasManyAtt.Schema = null;
  34:     }
  35: }

 

This is just a simple example, but there are a lot of problems you can solve be using the model visitors. Rhino Security uses this pattern to add caching to all the mappings, add schemas to models, and replaces interfaces in the model with actual entities.