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?
IStatefullProxy proxy = (IStatefullProxy)entity;
StateKeerp stateKeeper = proxy.StateKeeper;
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.
namespace StatefullEntityProxies
{
/// <summary>
/// Interface for a statefull proxy - a proxy for some entity that also holds a state keeper
/// that manages the state
/// </summary>
{
StateKeeper StateKeeper { get; }
}
}
namespace StatefullEntityProxies
{
/// <summary>
/// Keeps the state of an entity
/// </summary>
{
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);
}
}
}
}
}