NHibernate Forge
The official new home for the NHibernate for .NET community

Contextual data using NHibernate filters

Wiki Page Hierarchy

Pages

Page Details

First published by:
Germán Schuager
on 02-12-2009
Last revision by:
Germán Schuager
on 03-30-2011
4 people found this article useful.
Article
Comments (4)
History (7)
100% of people found this useful

Contextual data using NHibernate filters

I'm in the middle of the development process of an application using NH for data access, and I'm faced with a requirement that could be stated as follows:

The application needs to provide support for different Contexts of execution, and certain entities must be context-aware, which means that at a given time, the application only sees instances of these entities that correspond to the current context of execution.

Now, just remember that I have several entities defined that are used throught the entire application layer stack, so I wanted to solve this issue modifying as little as possible.

I'm very proud with the solution that I came up with, and also very amazed by the power of NHibernate.

To simplify a little lets assume that I have a static class that defines the current context of execution:

public enum ContexType
{
ContextA,
ContextB,
}
public static class Context
{
public static ContextType Current { get; set; }
}

Then, I create an interface that will be implemented by all the entities that need to be contextualized:

public interface IcontextAware
{
ContextType Context { get; set; }
}

Given a Cat class that needs to be contextualized, then I add the property to the class and to the mapping:

public class Cat : Entity, IcontextAware
{
...
ContextType Context { get; set; }
...
}
 
<class name="Cat">
...
<property name="Context">
...
</class>

The idea now, is to use the dynamic filtering capabilities of NHibernate to only retrieve the Cats instances corresponding with the current context every time that a query against Cat is issued.
Typically this means that I need to add a filter definition to the mappings and the specify the condition for that filter in every class mapping that need to be aware of this behavior.
But there is an easier way to do this automatically:

var filterParametersType = new Dictionary<string, Itype>(1);
filterParametersType.Add("current", NhibernateUtil.Enum(typeof(ContextType)));
cfg.AddFilterDefinition(new FilterDefinition("contextFilter", ":current = Context", filterParametersType));

foreach (var mapping in cfg.ClassMappings)
{
if (typeof(IContextAware).IsAssignableFrom(mapping.MappedClass))
{
mapping.AddFilter("contextFilter", ":current = Context");
}
}

Just do this (cfg is the NH Configuration object) before building the session factory and it creates the correct filter definition and adds the condition to every entity mapped that implements IContextAware.

At this point we just have our filter defined; now we need to enable it in order to actually filter something. It would be very handy if we can enable filtering at session factory scope, but since the session factory is immutable we need to enable it for each session that we will be using.

Wait.... maybe something else can do this work for us...

The following interceptor actually takes care of 2 things:
1. enables the context filter as soon as it is attached to the session, and
2. assigns the correct value to the Context property of entities implementing IContextAware when they are persisted.

public class ContextInterceptor : EmptyInterceptor
{
public override void SetSession(ISession session)
{
session.EnableFilter("contextFilter").SetParameter("current", Context.Current);
}

public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, IType[] types)
{
var contextAware = entity as IContextAware;
if (contextAware != null)
{
int index = Array.Find(propertyNames, 0, x => x.Equals("Context"));
state[index] = contextAware.Context = Context.Current;
return true;
}
return false;
}
}

Every session in the application needs to be created specifying this interceptor, but this should be an easy change (that depends on your architecture) if you are doing things right.

And thats all, the rest of the application is untouched and the requirement is fulfilled in a very elegant way.

(the original article is here)

Recent Comments

By: ColinBowern Posted on 09-09-2010 17:22

Just hit a snag with this approach where one of the context-aware objects was referenced by another class.  Filters don't seem to be applied to references when the EntityJoinWalker does it's magic.  According to the Hibernate folks filters should never apply in associations (see opensource.atlassian.com/.../HHH-4026).  Any thoughts?

By: Dor Rotman Posted on 01-12-2010 6:57

Excellent article!

By: nieve Posted on 12-20-2009 11:51

Great article indeed!!

However, unless I've missed a point there, it seems the article is somewhat dated- since NH maps enums to strings automatically nowadays, the current code throws an exception when it tries to convert the Context column value (of type nvarchar - string) into int in order to execute the WHERE @p = Context.

What I had to do in order for it to work was replace the filter parameter type adding with this:

filterParametersType.Add("current", NHibernateUtil.String);

and then the SetSession method was replaced with the following code:

session.EnableFilter("contextFilter").SetParameter("current", Context.Current.ToString());

Not a big change, but in case someone gets stuck on this :)

Otherwise, the only thing that I find a bit of a shame is that when replacing the filtering with something like

":current >= Context"

this works on alphabetical rather than enum/numeric order...

By: Jason Meckley Posted on 11-06-2009 18:29

great article!

I recently used filters with Monorail controller actions. Instead of an interceptor I created a Filter subclass to pull values from the engine context and apply the filter. works like a charm.

View All
Powered by Community Server (Commercial Edition), by Telligent Systems