Logo

NHibernate

The object-relational mapper for .NET

How to

This page is converted from the old nhforge.org Wiki. First published by: nadavsof on 09-11-2010, Last revision by: nadavsof on 09-11-2010

Keeping Entity State Over Multiple NHibernate Sessions

 

   Why would you want to use this source? You'll want to use this source if your using NHibernate and need your entities to maintain their state through long conversations, over multiple NHibernate sessions. You might also want to use it if you want built in auditing power and general state oriented methods implemented once and kept through the help of nhibernate.

The problem: NHibernate keeps track of entities within an NHibernate session (ISession). This means that if you fetch entities within a session, change those entities and flush the session, the session would know exactly what you changed, which works very well in most cases – when it is possible to re-fetch an entity within any new session, but sometimes this isn't enough. Example: multiple views that modifying the same entity instance, when any one of the views has a Save button, which is the time that the entity will actually be saved to the DB. In this case, we can't re-fetch the entity because it was changed and the DB (or cache) version is irrelevant. People will tell you – use a DTO, well, sometimes you gotta use a DTO (when serializing for example), but here, if we could just modify our previously fetched entity (it was fetched within an already disposed session), each time attaching it to the current session (to enable lazy loading), and at the end saving the changes with nhibernate knowing exactly what was changed (for dynamic-update for example), wouldn't it be great?

The solution - highlevel: Using dynamic proxies that keep the state of the entities. This way the entities are ignorant of their state being kept, while the proxies are in charge of keeping the entities' state, and the NHibernate session interceptor is in charge of using those proxies, to find out the previous state of an entity, so it is kept through multiple sessions. The only catch – each entity, when being instantiated, must be wrapped with a proxy.

The usage: 2 main classes concern the End-Developer:

1.NHibernateInterceptor – Implementor of the NHibernate.IInterceptor interface, which intercept certain events of the session (like FindDirty).

How should it be used?

When creating a session, the interceptor should be injected into the session. The interceptor also gets the session factory as a c'tor argument. The following code demonstrates its usage:

ISession session = sessionFactory.OpenSession(new NHibernateInterceptor(sessionFactory))

2.StatefullEntityProxyFactory – A factory to be used when instantiating an entity. This class is responsible for creating a proxy from an existing, or non existing entity instance. The proxy will appear to the application as if it is the actual entity, with one exception – the GetType() method. The proxy, although appears as the actual entity, can also be looked at as the statefull entity proxy.

How should it be used?

Instantiating an entity with a default 0-params c'tor:

Entity entity = new StatefullEntityProxyFactory().InstantiateEntityProxy<Entity>();

<Set values, do stuff>

session.SaveOrUpdate(entity);

Instantiating a proxy on an existing entity:

Entity entity = new Entity(1,"name");

entity = new StatefullEntityProxyFactory().InstantiateEntityProxy(entity) as Entity;

The above two examples are the use cases for instantiating a proxy instead of an entity, so that its state will be kept. These examples are shown for creating new entities. When an entity is fetched with NHibernate, the NHibernateInterceptor will do the same, so that the state will be kept for the nhibernate fetched entities. 

Ok, but why does it work?

  1. The StatefullEntityProxyInterceptor class is a LinFu dynamic proxies interceptor, which holds a reference to a StateKeeper instance and to an entity. It forwards all the calls (properties and methods) to the underlying entity, except for a call to get the StateKeeper. This call is possible if we do:

IStatefullProxy proxy = (IStatefullProxy)entity;

StateKeerp stateKeeper = proxy.StateKeeper;

  1. IStatefullProxy is an interface we're making the proxy implement (using the LinFu proxies API), and that defines a property of type StateKeeper

  2. The StateKeeper class has a previous state and a current state, and implements a FindDirty method, that finds the dirty properties (by comparing the previous and current states). The nhibernate interceptor, in its OnLoad event will set the previous state of the entity, and in the PostFlush it will call the InitState method of the state keeper. In the FindDirty, it will of course use the FindDirty method. Example (from the OnLoad event):

    IStatefullProxy proxy = (IStatefullProxy)entity;
    proxy.StateKeeper.InitState(propertyNames, state);

 

To fully understand how it works, it is recommended that you setup a small application with a couple of entities, map them with NHibernate, Instantiate them with the above described proxy factory, create the session injecting the interceptor, put breakpoints in every method of the NHibernateInterceptor and watch what's going on when you save\update\fetch\attach your entities to a different session.

 What else can be done with this idea?

 Well, lots of stuff. An example of a generic IsModified method is there – just get the underlying StateKeeper as shown in the examples above, and call IsModified. FindDirty, which returns the dirty property names can be useful for auditing as well, and the idea of IsModified can also be extended to implement RejectChanges.

 Technologies used: NHibernate 3.0 Alpha2, LinFu dynamic proxies. The source what not tested against NHibernate 2.1, but should work as no NH3 specific feature is being used.

 

 

The source:

 

 

namespace StatefullEntityProxies

{

    /// <summary>

    /// Interface for a statefull proxy - a proxy for some entity that also holds a state keeper

    /// that manages the state

    /// </summary>

    public interface IStatefullProxy

    {

        StateKeeper StateKeeper { get; }

    }

}

 

 

 

 

namespace StatefullEntityProxies

{

    /// <summary>

    /// Keeps the state of an entity

    /// </summary>

    public class StateKeeper

    {

        private object entity;

        public StateKeeper(object entity)

        {

            this.IsNew = true;

            this.entity = entity;

        }

 

        /// <summary>

        /// The previous state of the entity

        /// </summary>

        private IDictionary<string, object> previousState;

 

        /// <summary>

        /// The current state of the entity

        /// </summary>

        private IDictionary<string, object> currentState;

 

        /// <summary>

        /// Set the current state of the entity

        /// </summary>

        /// <param name="properties">The persisted properties</param>

        /// <param name="values">The corresponding values</param>

        /// <param name="state">The dictionary to populate</param>

        private void SetState(string[] properties, object[] values, IDictionary<string, object> state)

        {

            for (int i = 0; i < properties.Length; i++)

                state[properties[i]] = values[i];

        }

 

        /// <summary>

        /// Gets weather the entity is a new one

        /// </summary>

        public bool IsNew { get; protected set; }

 

        /// <summary>

        /// Init the state of the state keeper

        /// </summary>

        public void InitState()

        {

            this.previousState = this.currentState;

            this.currentState = null;

            this.IsNew = false;

        }

 

        /// <summary>

        /// Init the state of the state keeper with the current state of the entity

        /// </summary>

        /// <param name="properties">The persisted property names</param>

        /// <param name="values">The corresponding values</param>

        public void InitState(string[] properties, object[] values)

        {

            this.SetCurrentState(properties, values);

            this.InitState();

        }

 

        /// <summary>

        /// Sets the current state of the entity for the keeper

        /// </summary>

        /// <param name="properties">The persisted property names</param>

        /// <param name="values">The corresponding values</param>

        public void SetCurrentState(string[] properties, object[] values)

        {

            this.currentState = new Dictionary<string, object>();

            this.SetState(properties, values, this.currentState);

        }

 

        /// <summary>

        /// Sets the previous state of the entity for the keeper

        /// </summary>

        /// <param name="properties">The persisted property names</param>

        /// <param name="values">The corresponding values</param>

        public void SetPreviousState(string[] properties, object[] values)

        {

            this.previousState = new Dictionary<string, object>();

            this.SetState(properties, values, this.previousState);

        }

 

        /// <summary>

        /// Finds the dirty property names

        /// </summary>

        /// <returns>Enumeration of the dirty property names</returns>

        public IEnumerable<string> FindDirty()

        {

            if (this.IsNew)

            {

                return new string[0];

            }

 

            if (this.currentState == null)

            {

                this.ReadStateFromEntity();

            }

 

            List<string> dirtyProperties = new List<string>();

            foreach (KeyValuePair<string, object> property in this.previousState)

            {

                object previousValue = this.previousState[property.Key];

                object newValue = this.currentState[property.Key];

                if (this.IsChanged(previousValue, newValue))

                {

                    dirtyProperties.Add(property.Key);

                }

            }

 

            return dirtyProperties;

        }

 

        /// <summary>

        /// Check weather a specific property value is dirty

        /// </summary>

        /// <param name="previousValue">previous value</param>

        /// <param name="newValue">new value</param>

        /// <returns>true if its dirty</returns>

        private bool IsChanged(object previousValue, object newValue)

        {

            if (!NHibernateUtil.IsInitialized(previousValue) && !(NHibernateUtil.IsInitialized(newValue)))

            {

                return false;

            }

            else if (previousValue is AbstractPersistentCollection)

            {

                return (previousValue as AbstractPersistentCollection).IsDirty;

            }

            else

            {

                if (previousValue == null && newValue == null)

                    return false;

                else if (previousValue != null && newValue != null)

                    return !previousValue.Equals(newValue);

 

                // one of them is null and the other isn't

                return true;

            }

        }

 

        /// <summary>

        /// Finds the dirty properties corresponding indexes

        /// </summary>

        /// <param name="propertyNames">The property names</param>

        /// <returns>The indexes of the dirty properties</returns>

        public int[] FindDirtyIndexes(string[] propertyNames)

        {

 

            IEnumerable<string> dirtyProperties = this.FindDirty();

 

            if (dirtyProperties == null)

                return null;

 

            List<int> indexes = new List<int>();

 

            for (int i = 0; i < propertyNames.Length; i++)

            {

                if (dirtyProperties.Contains(propertyNames[i]))

                {

                    indexes.Add(i);

                }

            }

 

            return indexes.ToArray();

        }

 

        public bool IsModified

        {

            get

            {

                this.ReadStateFromEntity();

                if (this.FindDirty().Any())

                    return true;

                return false;

            }

        }

 

        private void ReadStateFromEntity()

        {

            this.currentState = new Dictionary<string, object>();

            foreach (string propertyName in this.previousState.Keys)

            {

                MemberInfo member =this.entity.GetType().GetMember(

                    propertyName, 

                    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)

                    .First();

                if (member is PropertyInfo)

                {

                    this.currentState[propertyName] = ((PropertyInfo)member).GetValue(this.entity, null);

                }

                else

                {

                    this.currentState[propertyName] = ((FieldInfo)member).GetValue(this.entity);

                }

            }

        }

    }

}

namespace StatefullEntityProxies
{
    /// <summary>
    /// A Statefull entity proxy that is constructed from an entity, forwarding all the calls to it
    /// and a statekeeper that can be retrieved by the IStatefullProxy interface
    /// </summary>

    public class StatefullEntityProxyInterceptor : global::LinFu.DynamicProxy.IInterceptor

    {
        private StateKeeper stateKeeper;
        private object entity;
        /// <summary>
        /// Initiates the proxy with a given entity instance
        /// </summary>
        /// <param name="entity">The entity that its state will be managed by this proxy</param>
        public StatefullEntityProxyInterceptor(object entity)
        {
            this.stateKeeper = new StateKeeper(entity);
            this.entity = entity;
        }
        /// <summary>
        /// Part of the linfu interceptor interface. This method is called for each virtual property or method call on the entity
        /// </summary>
        /// <param name="info">The invocation info on the current call</param>
        /// <returns>Return the return value of the method/property call</returns>
        public object Intercept(LinFu.DynamicProxy.InvocationInfo info)
        {
            // If its a call to get the state keeper, return the proxy's underlying state keeper
            if (info.TargetMethod.Name == "get_StateKeeper")
                return this.stateKeeper;
            // else, forward the call to the actual entity
            return info.TargetMethod.Invoke(this.entity, info.Arguments);
        }
        public override int GetHashCode()
        {
            return this.entity.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return this.entity.Equals(obj);
        }
        
    
    }
}
namespace StatefullEntityProxies
{
    /// <summary>
    /// Factory for generating entity proxies
    /// </summary>

    public class StatefullEntityProxyFactory

    {
        private static readonly global::LinFu.DynamicProxy.ProxyFactory factory =
              new global::LinFu.DynamicProxy.ProxyFactory();
        /// <summary>
        /// Creates a proxy from an existing entity instance
        /// </summary>
        /// <param name="entity">The entity to proxy</param>
        /// <returns>The proxied entity</returns>
        public IStatefullProxy InstantiateEntityProxy(object entity)
        {
            // Creates a new proxy interceptor
            StatefullEntityProxyInterceptor stateKeeperProxy = new StatefullEntityProxyInterceptor(entity);
            // Generate a proxy, with the statefull entity interceptor as the interceptor, and the entity type as the derrived type
            return (IStatefullProxy)factory.CreateProxy(entity.GetType(), stateKeeperProxy, typeof(IStatefullProxy));
        }
        /// <summary>
        /// Creates a proxy for the given type using the default empty params c'tor
        /// </summary>
        /// <param name="objectType">Type to create a proxy for</param>
        /// <returns>The generated proxy</returns>
        public IStatefullProxy InstantiateEntityProxy(Type objectType)
        {
            object objectToProxy = objectType.GetConstructor(new Type[0]).Invoke(new object[0]);
            return this.InstantiateEntityProxy(objectToProxy);
        }
        /// <summary>
        /// Creates a proxy for the given type using the default empty params c'tor
        /// </summary>
        /// <typeparam name="TEntity">The type to craete proxy for</typeparam>
        /// <returns>The proxy</returns>
        public TEntity InstantiateEntityProxy<TEntity>() where TEntity : class
        {
            return (TEntity)(this.InstantiateEntityProxy(typeof(TEntity)));
        }
    }
}
namespace StatefullEntityProxies
{
    /// <summary>
    /// Interceptor that intercept events of the session that it is binds to, and assumes that all entities are actually
    /// statefull entity proxies, and uses the underlying state keeper to determine the previous state of entities
    /// so that the state is kepts even through different sessions
    /// </summary>

    public class NHibernateInterceptor : EmptyInterceptor

    {
        /// <summary>
        /// Creates a new interceptor
        /// </summary>
        /// <param name="sessionFactory">The session factory that is used to generate the current session</param>
        public NHibernateInterceptor(ISessionFactory sessionFactory)
        {
            this.Factory = sessionFactory as ISessionFactoryImplementor;
        }
        private ISessionFactoryImplementor Factory {get;set;}
        /// <summary>
        /// Creates a new entity instance using the statefull entity proxy factory
        /// </summary>
        /// <remarks>The call to this method is made by nhibernate when loading entities from queries</remarks>
        public override object Instantiate(string clazz, EntityMode entityMode, object id)
        {
            // Get the type of the entity
            Type type = Type.GetType(clazz);
            // Create a new instance of the entity using the persister of the entity
            object entity = this.Factory.GetEntityPersister(clazz).Instantiate(id, entityMode);
            // Create a proxy from the entity
            object proxy = new StatefullEntityProxyFactory().InstantiateEntityProxy(entity);
            return proxy;
        }
        /// <summary>
        /// Initiate the state of the proxy when an entity is loaded
        /// </summary>
        /// <remarks>This happens after the instantiation of an entity (a proxy)</remarks>
        public override bool OnLoad(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
        {
            IStatefullProxy proxy = (IStatefullProxy)entity;
            proxy.StateKeeper.InitState(propertyNames, state);
            return base.OnLoad(entity, id, state, propertyNames, types);
        }
        /// <summary>
        /// Finds the dirty properties of the entity. Uses the proxy get the dirty properties
        /// </summary>
        /// <remarks>When updating an entity in a different session, the previousState argument would be null!!</remarks>
        public override int[] FindDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, NHibernate.Type.IType[] types)
        {
            IStatefullProxy proxy = (IStatefullProxy)entity;
            proxy.StateKeeper.SetCurrentState(propertyNames, currentState);
            return proxy.StateKeeper.FindDirtyIndexes(propertyNames);
        }
        /// <summary>
        /// Checks weather the given entity is transient using the proxy.
        /// </summary>
        public override bool? IsTransient(object entity)
        {
            IStatefullProxy proxy = (IStatefullProxy)entity;
            return proxy.StateKeeper.IsNew;          
        }
        /// <summary>
        /// After a flush of an entity, the init state must be called, because the current Current value is actually the new previous value
        /// </summary>
        public override void PostFlush(System.Collections.ICollection entities)
        {
            foreach (IStatefullProxy proxy in entities)
            {
                proxy.StateKeeper.InitState();
            }
        }
        /// <summary>
        /// Gets the entity name
        /// </summary>
        /// <remarks>Because the entity is actually a proxy, we gotta return the base type of the entity, which is the actual entity type</remarks>
        public override string GetEntityName(object entity)
        {
            return entity.GetType().BaseType.FullName;
        }
    }
}

 

© NHibernate Community 2024