Override interface mappings and creata a generic entity version filter

Introduction

For one of my customers I needed to have some kind of versioning for a whole bunch of entities on my database. Since I use my very own MvcExtensions framework, I wanted to include a functionality which would automatically filter all entities based on this interface :


    public interface IVersionAware
    {
        string Version { get; set; }
    }

In short, I only want to get the entities of the current version through my repository. This interface would then automaticly imply that all data could be filtered by setting a version filter.

I took me quite some time, but spending a weekend at the Dutch coast cleared my mind a bit, and after the weekend I managed to get it working...

The void (tm)

Hit the road, Jack !!

Ok, so how can we do this ?

In MvcExtensions I map my domain classes to my database in the following way :


    public class MyDomainDefinition: IDomainDefinition
    {
        public MyDomainDefinition() { }

        public Assembly DomainAssembly
        {
            get { return typeof(Model.Kit).Assembly; }
        }

        public DomainType GetDomainType(Type t)
        {
            if (t.Namespace==typeof(Model.Kit).Namespace)
                return DomainType.Class;
            else
                return DomainType.None;
        }

        //if !=null write mapping files to disk
        public string WriteHbmFilesToPath
        {
            //get { return @"c:\tmp"; }
            get { return null; }
        }
    }

This might look quite simple, but it is an extensive abstraction layer over fluent nhibernate which takes away most of the complexity, and adds a lot of functionality (bitmap storage, multilingual fields to name a few).
After looking around for some info on the net about filtering using fluent nhbernate, I found a solution: one could define a system-wide filter using the following Filterdefinition and mapping.ApplyFilter :


    public class VersionFilter : FilterDefinition
    {
        public static readonly string FILTERNAME = "MyVersionFilter";
        public static readonly string COLUMNNAME = "Version";
        public static readonly string CONDITION = COLUMNNAME + " = :" + COLUMNNAME;

        public VersionFilter()
        {
            WithName(FILTERNAME)
              .WithCondition(CONDITION)
              .AddParameter(COLUMNNAME, NHibernateUtil.String);
        }

        public static void Enable(NHibernate.ISession session, string version)
        {
            session.EnableFilter(FILTERNAME).SetParameter(COLUMNNAME, version);
        }

        public static void Disable(NHibernate.ISession session)
        {
            session.DisableFilter(FILTERNAME);
        }
    }

And this would be the code to map it for some class.


automapping.Override<SomeClassImplementingIVersionAware>(
  mapping=>
  {
    mapping.Id(x=>x.Id);
    mapping.Map(x=>x.Version).Column(VersionFilter.COLUMNAME);
    mapping.ApplyFilter<VersionFilter>();
  }

Since I do not want to call this override for each class implementing IVersionAware, I decided to create a workaround. I spent quite some time in trying to implement this, until I noticed why my workaround was not working: there was a (small bug) in the fluentnhibernate framework.
So I forked fluentnhibernate, fixed the bug, and sent a pull request to the fluentnhibernate guys, and Paul Batum reported to me that my bugfix will be integrated into the next merge, together with some unit tests. For now, you can get the fixed sourcecode from my github version.

... And don't you come back no more....

So, after fixing this, I could get to work again. Since I first tried using IAlterations and Overrides for fluent nhibernate, but all were failing due to the bug, I finally got to this code (which worked after I fixed the bug in fluent nhibernate):


    public static class AutoPersistenceModelExtensions
    {
        public static AutoPersistenceModel OverrideInterface<I,C>(this AutoPersistenceModel model
            , IEnumerable<Type> MyTypes)
            where C:IInterfaceMap<I>,new()
        {
            var modeloverride = model.GetType().GetMethod("Override");
            var interfacemap = new C();
            var cm = typeof(C).GetMethod("Map");
            foreach (var t in MyTypes.Where(x => typeof(I).IsAssignableFrom(x)))
            {
                var act = cm.MakeGenericMethod(t).Invoke(interfacemap,null);
                modeloverride.MakeGenericMethod(t).Invoke(model, new object[] { act });
            }
            return model;
        }
    }

    public interface IInterfaceMap<I>
    {
        Action<AutoMapping<T>> Map<T>() where T:I;
    }

This extension method allows you to override the mappings for interface fields in a very simple way; here is the example for the IVersionAware interface:


    public class VersionAwareInterfaceMap : IInterfaceMap<IVersionAware>
    {
        public Action<AutoMapping<T>> Map<T>() where T : IVersionAware
        {
            return mapping =>
                {
                    mapping.Map(x => x.Version).Column(VersionFilter.COLUMNNAME);
                    mapping.ApplyFilter<VersionFilter>();
                };
        }
    }

And in the MvcExtensions database bootstrapper, I would call this :


        protected Database(IPersistenceConfigurer pcfg, IDomainDefinition mappings)
        {
             // some stuff deleted because it is not relevant here
            var cfg = Fluently.Configure()
                .Database(pcfg)
                .Mappings(m => {
                    var am1 = AutoMap.Assembly(mappings.DomainAssembly)
                        .Where(t => clsmaps.Contains(mappings.GetDomainType(t)))
                        // some stuff deleted because it is not relevant here
                        );
                    var types = mappings.DomainAssembly.GetTypes().Where(x =>
                                typeof(IVersionAware).IsAssignableFrom(x) &&
                                mappings.GetDomainType(x) != DomainType.None);
                    if (types.Count()>0)
                    {
                        am1.OverrideInterface<IVersionAware,VersionAwareInterfaceMap>(types);
                        am1.Add(new VersionFilter());
                    }
                        // some stuff deleted because it is not relevant here

                })
                .BuildConfiguration();
            // some stuff deleted because it is not relevant here
        }

... No more ....

In order to filter my data I would call this :


VersionFilter.Enable(sUnitOfWork.Session,"v1.3.2");

And disabling it again goes like this :


VersionFilter.Disable(sUnitOfWork.Session);

That's all there is to it; all entities implementing the IVersionAware interface will be automatically filtered if you do set the filter....

For a full version you can check out the sourceode over at github.

If you have any questions or remarks, do not hesitate to post them...

Enjoy

Bookmark and Share