Click here to Skip to main content
Click here to Skip to main content

Override interface mappings and creata a generic entity version filter in fluent nhibernate

, 21 Apr 2010
Rate this:
Please Sign up or sign in to vote.
Override interface mappings and creata a generic entity version filter in fluent nhibernate

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 NHibernate, 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 source code 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 source code over at github.

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

Enjoy!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Tom Janssens
Founder Core bvba
Belgium Belgium
Tom Janssens, owner of Core, a software and consultancy company.
Father of two sons named Quinten & Matisse, and married to a beautiful woman named Liesbeth.
 
Blog: http://tojans.me
Github: http://github.com/ToJans
Twitter: http://twitter.com/ToJans
LinkedIn: http://www.linkedin.com/in/tomjanssens

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web02 | 2.8.140721.1 | Last Updated 21 Apr 2010
Article Copyright 2010 by Tom Janssens
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid