INotifyPropertyChang{ed,ing} and NHibernate

(Updated: thanks, Tom!) 

Here's the scenario: your domain model implements INotifyPropertyChanged and INotifyPropertyChanging, so you want to be notified whenever your properties change. NHibernate has an interceptor model that can be used with, together with code generators, to do this transparently.

First, let's define a custom NHibernate interceptor that is also a Castle Project interceptor:

public class NotifyPropertyChangeInterceptor : EmptyInterceptor, Castle.Core.Interceptor.IInterceptor

{

    private static readonly ProxyGenerator proxyGenerator = new ProxyGenerator();

    public NotifyPropertyChangeInterceptor(ISessionFactory sessionFactory)

    {

        this.SessionFactory = sessionFactory;

    }

    public ISessionFactory SessionFactory

    {

        set;

        private get;

    } 

    public override String GetEntityName(Object entity)

    {

        if (entity.GetType().Assembly.FullName.StartsWith("DynamicProxyGenAssembly2") == true)

        {

            return(entity.GetType().BaseType.FullName); 

        }

       else

        {

            return(entity.GetType().FullName);

        }

    }

    public override Object Instantiate(String clazz, EntityMode entityMode, Object id)

    {

        if (entityMode == EntityMode.Poco)

        {

            Type type = Type.GetType(clazz, false);

            if (type != null)

            {

                Object instance = this.create(type);

                this.SessionFactory.GetClassMetadata(clazz).SetIdentifier(instance, id, entityMode);

                return (instance);

            }

        }

        return (base.Instantiate(clazz, entityMode, id));

    }

    void Castle.Core.Interceptor.IInterceptor.Intercept(IInvocation invocation)

    {

        if (typeof(INotifyPropertyChanging).IsAssignableFrom(invocation.Method.DeclaringType) == true)

        {

            if (invocation.Method.Name.StartsWith("set_") == true)

            {

                String propertyName = invocation.Method.Name.Substring(4);

                FieldInfo field = invocation.InvocationTarget.GetType().BaseType.GetField("PropertyChanging", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);

                MulticastDelegate evt = field.GetValue(invocation.InvocationTarget) as MulticastDelegate;

                if (evt != null)

                {

                    foreach (Delegate del in evt.GetInvocationList())

                    {

                        del.Method.Invoke(invocation.InvocationTarget, new Object [] { invocation.InvocationTarget, new PropertyChangingEventArgs(propertyName) });

                    }

                }

            }

        }

        invocation.Proceed();

        if (typeof(INotifyPropertyChanged).IsAssignableFrom(invocation.Method.DeclaringType) == true)

        {

            if (invocation.Method.Name.StartsWith("set_") == true)

            {

                String propertyName = invocation.Method.Name.Substring(4);

                FieldInfo field = invocation.InvocationTarget.GetType().BaseType.GetField("PropertyChanged", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);

                MulticastDelegate evt = field.GetValue(invocation.InvocationTarget) as MulticastDelegate;

                if (evt != null)

                {

                    foreach (Delegate del in evt.GetInvocationList())

                    {

                        del.Method.Invoke(invocation.InvocationTarget, new Object [] { invocation.InvocationTarget, new PropertyChangedEventArgs(propertyName) });

                    }

                }

            }

        }

    }

    private Object create(Type type)

    {

        return (proxyGenerator.CreateClassProxy(type, new Type [] { typeof(INotifyPropertyChanged), typeof(INotifyPropertyChanging) }, this));

    }

}

You class is something like this:

[Serializable]

public class SomeClass : INotifyPropertyChanged, INotifyPropertyChanging

{

    public virtual Int32 Id

    {

        get;

        set;

    }

    public virtual String SomeProperty

    {

        get;

        set;

    }

    public virtual event PropertyChangedEventHandler PropertyChanged;

    public virtual event PropertyChangingEventHandler PropertyChanging;

}

And this is how you would use it:

using (ISessionFactory factory = cfg.BuildSessionFactory())

{

    NotifyPropertyChangeInterceptor interceptor = new NotifyPropertyChangeInterceptor(factory);

 

    using (ISession session = factory.OpenSession(interceptor))

    using (ITransaction tx = session.BeginTransaction())

    {

        SomeClass c = session.Load<A>(1);

        c.PropertyChanged += delegate(Object sender, PropertyChangedEventArgs e) { Console.WriteLine("Property {0} Changed", e.PropertyName); };

        c.SomeProperty = "blah";

    }

Bookmark and Share

                             

12 Comments

  • I've used your code, but I got problem with storing object into database. Nhibernate reports missing persister for some class Proxy objects.

    I've fixed this by storing original class name and proxy class name in a dictionary and returning base name in override method:
    public override string GetEntityName(object entity)

    Because I'm beginner in nhibernate I'd like to know whether my solution is correct?



    btw. great piece of code ;)

  • Tom,

    Yes, that is the solution. GetEntityName exists just for that.

    RP

  • I ran into: Object does not match target

    and had to change:
    del.Method.Invoke(invocation.InvocationTarget, new Object[] { invocation.InvocationTarget, new PropertyChangedEventArgs(propertyName) });

    to
    del.Method.Invoke(del.Target, new Object[] { invocation.InvocationTarget, new PropertyChangedEventArgs(propertyName) });

    in both places above...did you run into the same thing or do you think it was it because in my test secenario the delegate was added to the object after it was cast to INotifyPropertyChanged?

  • Hello, Gary!
    You can find a fully working version on my SkyDrive. It was the example I used in my TechDays presentation: http://cid-0450c015fc418de2.skydrive.live.com/browse.aspx/.Public/TechDays%202010.
    The following topics are covered:
    - The various inheritance mapping strategies (Animals project);
    - Sets and Lists;
    - Listeners for intercepting a save and setting some properties' values;
    - The various query options;
    - NHibernate Validator validation;
    - NHibernate Search indexing (you'll have to index the entities yourself, I'm afraid, I didn't include the index files);
    - User types (an image user type).
    The SQL script is at the root of the project, if you have any trouble, drop-me a line.
    Regards,
    RP

  • HI
    the method Istantiate at line

    Type type = Type.GetType(clazz, false);

    return always null...! if the type is in another assembly;

    i find that if i put the assemblyqualifiedname GetType method return an instance of type correctly

    but nhibernate engine in the parameter clazz don't put assemblyqualified name

    you can hel me? thanks

    sorry for my english

  • Tonio,

    You should place the assembly qualified name in the call to GetType(). If not, it won't find the type if it is another assembly. You can, however, add an event handler to event AppDomain.TypeResolve and, in it, go through all assemblies loaded for the current AppDomain with a call to AppDomain.CurrentDomain.GetAssemblies() and check them all for the type you're after.

  • Hi,

    "go through all assemblies loaded for the current AppDomain with a call to AppDomain.CurrentDomain.GetAssemblies() and check them all for the type"

    ok.. then I decide to create a static class witch expose a static IDictionary witch contains all public types in my app domain assemblies with full name as key

    I think it is also more efficient...

  • Hi,

    now my entity implements INotifyPropertyChang trough Nhibernate

    ...but if i wish intercept a propertychanged event (OnPropertyChanged) in my viewmodel how i can do it?

    I wish to know when user change some property to decide if forced user to apply or cancel edit action

    thanks

    t

  • ((INotifyPropertyChanged)Utente).PropertyChanged
    += (sender, args) => ... ;

    thanks :)

  • Just a comment about the Type.GetType(clazz) returning null in the Instantiate method that the first commenter complained about: you can find out the real type of "clazz" from the SessionFactory, like so: SessionFactory.GetAllClassMetadata()[clazz].GetMappedClass(entityMode).
    This will let you find the real type regardless of whether it is in the same assembly or not (which is a restriction on Type.GetType())

  • @Ilya:

    Thanks. The best way, I think, is
    ISession.GetSessionImplementation().BestGuessEntityName(entity)

  • Well, let's be clear, I don't acaltuly think PowerShell is acaltuly bad and this isn't meant to be a criticism of just the PowerShell team.When developers claim that bugs are intentional I think it lowers people's respect for them, and for all software developers, and lowers expectations for software (which is saying something). How about if I put it this way: it's one thing to say we could not find a way to make that case work and still handle everything else we need to handle, or even: we didn't have time to finish that yet, and we've prioritized other things ... but it's another thing to say we meant to do that. Declaring something to be by design sounds like we meant to do that and we really need to avoid it unless we can provide an explanation that will convince the user that our design is better than what they are asking for.

Comments have been disabled for this content.