Archive for July 2008

Advanced ActiveRecord Models – Part 2

In part 1, I discussed what Castle ActiveRecord does when starting up and ways you can modify the model after is has been generated.  Another useful trick is to add custom attributes to your model that can be inspected while ActiveRecord is inspecting its model.  To this you need to create a class that implements Castle.ActiveRecord.Framework.Internal.IModelBuilderExtension and register it with the ActiveRecordStarter.

 

For my example, let’s assume that we want to implement soft deletes using ActiveRecord.  The NHibernate FAQ has a great blog entry on how to make NHibernate mark a entity as deleted as opposed to deleting it from the database.  Assuming you are using their model of marking your entities as ISoftDeleteable, here are two things we can do to extend this model:

 

  1. If session.Get(id) is called on soft deleted record, null is returned.
  2. If a collection contains a record that has been soft deleted, that record will not show up when the collection is loaded.

 

I am not entirely sure that these two features are something that you want to necessarily do, however, they are good examples for what I am trying to show.  The reason I say that is because I am not sure you want entities showing up sometimes and not other times, i.e. a soft deleted model would show up for a custom query unless someone checks the Deleted flag, how do soft deletes work across entity graphs, should we allow both ends of a bidirectional relationship to be soft deleteable, etc.  So, I have kept this example limited too two easy features, but remember to think about this a little more before actually implementing it.  So on to the example, the first thing we need to is to add a custom model builder to inspect our model:

 

   1: public class SoftDeleteModelBuilderExtension : IModelBuilderExtension
   2: {
   3:  
   4:     public static IDictionary<Type, SoftDeleteModel> SoftDeleteModels = new Dictionary<Type, SoftDeleteModel>();
   5:  
   6:     #region IModelBuilderExtension Members
   7:  
   8:     public void ProcessBelongsTo(System.Reflection.PropertyInfo pi, BelongsToModel belongsToModel, ActiveRecordModel model)
   9:     {
  10:     }
  11:  
  12:     public void ProcessClass(Type type, ActiveRecordModel model)
  13:     {
  14:         GetModel(type, model);
  15:     }
  16:  
  17:     public void ProcessField(System.Reflection.FieldInfo fi, ActiveRecordModel model)
  18:     {
  19:     }
  20:  
  21:     public void ProcessHasAndBelongsToMany(System.Reflection.PropertyInfo pi, HasAndBelongsToManyModel hasAndBelongManyModel, ActiveRecordModel model)
  22:     {
  23:         if (!IsSoftModel(hasAndBelongManyModel.HasManyAtt.MapType)) return;
  24:         var softDeleteModel = GetModel(model.Type, model);
  25:         softDeleteModel.HasAndBelongsToMany.Add(hasAndBelongManyModel);
  26:     }
  27:  
  28:     public void ProcessHasMany(System.Reflection.PropertyInfo pi, HasManyModel hasManyModel, ActiveRecordModel model)
  29:     {
  30:         if (!IsSoftModel(hasManyModel.HasManyAtt.MapType)) return;
  31:         var softDeleteModel = GetModel(model.Type, model);
  32:         softDeleteModel.HasMany.Add(hasManyModel);
  33:     }
  34:  
  35:     public void ProcessHasManyToAny(System.Reflection.PropertyInfo pi, HasManyToAnyModel hasManyModel, ActiveRecordModel model)
  36:     {
  37:         if (!IsSoftModel(hasManyModel.HasManyToAnyAtt.MapType)) return;
  38:         var softDeleteModel = GetModel(model.Type, model);
  39:         softDeleteModel.ManyToAny.Add(hasManyModel);
  40:     }
  41:  
  42:     public void ProcessProperty(System.Reflection.PropertyInfo pi, ActiveRecordModel model)
  43:     {
  44:     }
  45:  
  46:     private static bool IsSoftModel(Type type)
  47:     {
  48:         return (typeof(ISoftDeleteable).IsAssignableFrom(type));
  49:     }
  50:  
  51:     private static SoftDeleteModel GetModel(Type type, ActiveRecordModel model)
  52:     {
  53:         SoftDeleteModel softDeleteModel;
  54:         SoftDeleteModels.TryGetValue(type, out softDeleteModel);
  55:  
  56:         if (softDeleteModel == null)
  57:         {
  58:             softDeleteModel = new SoftDeleteModel {Type = type, ActiveRecordModel = model};
  59:             if (IsSoftModel(type))
  60:                 softDeleteModel.SoftDeleteable = true;
  61:             SoftDeleteModels[type] = softDeleteModel;
  62:         }
  63:         return softDeleteModel;
  64:     }
  65:  
  66:     #endregion
  67: }

 

   1: public class SoftDeleteModel
   2: {
   3:     /// <summary>
   4:     /// Gets or sets the type.
   5:     /// </summary>
   6:     /// <value>The type.</value>
   7:     public Type Type { get; set; }
   8:  
   9:     /// <summary>
  10:     /// Gets or sets a value indicating whether the model is soft deleteable.
  11:     /// </summary>
  12:     /// <value><c>true</c> if soft deleteable; otherwise, <c>false</c>.</value>
  13:     public bool SoftDeleteable { get; set; }
  14:  
  15:     /// <summary>
  16:     /// Gets or sets the active record model for this type.
  17:     /// </summary>
  18:     /// <value>The active record model.</value>
  19:     public ActiveRecordModel ActiveRecordModel { get; set; }
  20:  
  21:     private readonly HashSet<HasManyModel> _hasMany = new HashSet<HasManyModel>();
  22:  
  23:     /// <summary>
  24:     /// Gets the has many relationships to a soft deleted type.
  25:     /// </summary>
  26:     /// <value>The has many.</value>
  27:     public HashSet<HasManyModel> HasMany
  28:     {
  29:         get { return _hasMany; }
  30:     }
  31:  
  32:     private readonly HashSet<HasAndBelongsToManyModel> _hasAndBelongsToMany = new HashSet<HasAndBelongsToManyModel>();
  33:  
  34:     /// <summary>
  35:     /// Gets the has and belongs to many relationships to a soft deleted type.
  36:     /// </summary>
  37:     /// <value>The has and belongs to many.</value>
  38:     public HashSet<HasAndBelongsToManyModel> HasAndBelongsToMany
  39:     {
  40:         get { return _hasAndBelongsToMany; }
  41:     }
  42:  
  43:     private readonly HashSet<HasManyToAnyModel> _manyToAny = new HashSet<HasManyToAnyModel>();
  44:  
  45:     /// <summary>
  46:     /// Gets the many to any relations that belong to a soft deleted type.
  47:     /// </summary>
  48:     /// <value>The many to any.</value>
  49:     public HashSet<HasManyToAnyModel> ManyToAny
  50:     {
  51:         get { return _manyToAny; }
  52:     }
  53: }


As you can see, as the models as being inspected, we are building up a model of class that are soft deleted and classes that have collections of classes that are soft deleted.  To add our custom model inspector to ActiveRecord simply do the following line before calling ActiveRecordStarter.Initialize():

 

   1: ActiveRecordStarter.RegisterExtension(new SoftDeleteModelBuilderExtension());

 

Now, that we have our soft delete model we need to remove any of the collections that are mapped to ISoftDeleteable entities.  We are going to add these collections back later though.  To remove these collections, we need to write a model visitor that I discussed in Part 1:

 

   1: public class SoftRelationshipsRemovalVisitor : AbstractDepthFirstVisitor
   2: {
   3:     private readonly HashSet<ActiveRecordModel> _visited = new HashSet<ActiveRecordModel>();
   4:  
   5:     /// <summary>
   6:     /// Visits the model.
   7:     /// </summary>
   8:     /// <param name="model">The model.</param>
   9:     public override void VisitModel(ActiveRecordModel model)
  10:     {
  11:         if (!_visited.Contains(model))
  12:             _visited.Add(model);
  13:         else
  14:             return;
  15:  
  16:         if (SoftDeleteModelBuilderExtension.SoftDeleteModels.ContainsKey(model.Type))
  17:         {
  18:             var softDeleteModel = SoftDeleteModelBuilderExtension.SoftDeleteModels[model.Type];
  19:  
  20:             foreach (var hasMany in softDeleteModel.HasMany)
  21:                 model.HasMany.Remove(hasMany);
  22:  
  23:             foreach (var hasAndBelongsToManyModel in softDeleteModel.HasAndBelongsToMany)
  24:                 model.HasAndBelongsToMany.Remove(hasAndBelongsToManyModel);
  25:  
  26:             foreach (var manyToAny in softDeleteModel.ManyToAny)
  27:                 model.HasManyToAny.Remove(manyToAny);
  28:         }
  29:     }
  30: }

 

And to register the visitor:

 

   1: ModelsDelegate validated = null;
   2: validated = delegate(ActiveRecordModelCollection models, IConfigurationSource source)
   3: {
   4:     ActiveRecordStarter.ModelsValidated -= validated;
   5:     foreach (ActiveRecordModel model in models)
   6:     {
   7:         model.Accept(new SoftRelationshipsRemovalVisitor());
   8:     }
   9: };
  10: ActiveRecordStarter.ModelsValidated += validated;

 

Now when our entities are generated by ActiveRecord, they will no longer have mapped collections that go to ISoftDeleteable entities.  Now that they are gone what next?  In Part 3,  I will examine how to add back the collections we removed so that they take into account the soft deleted entities. At the end of this series, I will post the full source code for the solution.

Injecting dependencies into types that are not components using Windsor

Sometimes I find the need to inject dependencies from the container into a type that is not registered as a component. It does not come up very often, but mainly it is something that I need when writing tests and it does not make sense to register my object with the container.  Turns out this is pretty easy with Windsor:

 

   1: public static class IKernelExtensions
   2: {
   3:  
   4:     public static T Autowire<T>(this IKernel kernel)
   5:     {
   6:         ComponentModel component = kernel.ComponentModelBuilder.BuildModel(typeof(T).FullName, typeof(T), typeof(T), null);
   7:         var activator = kernel.CreateComponentActivator(component);
   8:         return (T)activator.Create(CreationContext.Empty);
   9:     }
  10:  
  11: }
  12:  
  13: [TestClass]
  14: public class IKernelExtensionsTests 
  15: {
  16:     [TestMethod, ExpectedException(typeof(ComponentNotFoundException)]
  17:     public void Autowire_creates_an_object_with_adding_to_container()
  18:     {
  19:         var kernel = new DefaultKernel();
  20:         kernel.AddComponent("test", typeof(IServiceInterface), typeof(ServiceInferface));
  21:         var someService = kernel.Autowire<SomeService>();
  22:         Assert.IsNotNull(someService.ServiceInterface);
  23:         kernel.Resolve<SomeService>();
  24:         Assert.Fail();
  25:     }
  26:  
  27:     [TestMethod, ExpectedException(typeof(ComponentNotFoundException))]
  28:     public void Autowire_creates_an_object_with_adding_to_container_using_constructur()
  29:     {
  30:         var kernel = new DefaultKernel();
  31:         kernel.AddComponent("test", typeof(IServiceInterface), typeof(ServiceInferface));
  32:         var someService = kernel.Autowire<SomeServiceCtorInjection>();
  33:         Assert.IsNotNull(someService.ServiceInterface);
  34:         kernel.Resolve<SomeServiceCtorInjection>();
  35:         Assert.Fail();
  36:     }
  37:  
  38:     public class SomeService
  39:     {
  40:         public IServiceInterface ServiceInterface { get; set; }
  41:     }
  42:  
  43:     public class SomeServiceCtorInjection
  44:     {
  45:         public SomeServiceCtorInjection(IServiceInterface serviceInterface)
  46:         {
  47:             ServiceInterface = serviceInterface;
  48:         }
  49:  
  50:         public IServiceInterface ServiceInterface { get; private set; }
  51:     }
  52:  
  53:     public interface IServiceInterface
  54:     {
  55:     }
  56:  
  57:     public class ServiceInferface : IServiceInterface
  58:     {    
  59:     }
  60: }

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.