NHibernate event listeners
In this article we provide 4 examples of event listeners that use IEntityEventsListener extension point:
- OnPreUpdate listener to check if the Annual Revenue property of the Contractor object is filled in;
- OnPostUpdate listener to record the history of contractors’ name changes;
- OnPreUpdateCollection listener to record the history of adding/removing contractors’ phone numbers;
- OnPostUpdateCollection listener to check if the contractor’s phone number has invalid symbols.
Pre-events happen before the transaction is committed to the database. They allow us to execute our custom logic before the update/insert is sent to the database. In terms of ELMA, you can use it, for example, to check if the object’s properties are filled in correctly.
Post-events happen after the transaction is committed to the database. They allow us to execute our custom logic after the update/insert is sent to the database. In ELMA, you can use them, for example, to add a new record to the history of changes.
Pre/PostUpdateCollection events must be used when you need to track actions with 1-N collections. In this article, we will show you how to get new/old collection’s items.
All events from IEntityEventsListener occur when you call the Save() method of an object.
Members of EntityEventsListener extension point
IEntityEventsListener extension point has the following members:
/// <summary>
/// Before inserting
/// </summary>
/// <param name="event">Event parameters</param>
/// <returns><c>true</c>, if it is needed to interrupt operation</returns>
public virtual bool OnPreInsert(PreInsertEvent @event)
/// <summary>
/// After inserting
/// </summary>
/// <param name="event">Event parameters</param>
public virtual void OnPostInsert(PostInsertEvent @event)
/// <summary>
/// Before updating
/// </summary>
/// <param name="event">Event parameters</param>
/// <returns><c>true</c>, if it is needed to interrupt operation</returns>
public virtual bool OnPreUpdate(PreUpdateEvent @event)
/// <summary>
/// After updating
/// </summary>
/// <param name="event">Event parameters</param>
public virtual void OnPostUpdate(PostUpdateEvent @event)
/// <summary>
/// Before deleting
/// </summary>
/// <param name="event">Event parameters</param>
/// <returns><c>true</c>, if it is needed to interrupt operation</returns>
public virtual bool OnPreDelete(PreDeleteEvent @event)
/// <summary>
/// After deleting
/// </summary>
/// <param name="event">Event parameters</param>
public virtual void OnPostDelete(PostDeleteEvent @event)
/// <summary>
/// After deleting the element in collection
/// </summary>
/// <param name="event">Event parameters</param>
public virtual void OnPostRemoveCollection(PostCollectionRemoveEvent @event)
/// <summary>
/// After recreating the element in collection
/// </summary>
/// <param name="event">Event parameters</param>
public virtual void OnPostRecreateCollection(PostCollectionRecreateEvent @event)
/// <summary>
/// After updating the element in collection
/// </summary>
/// <param name="event">Event parameters</param>
public virtual void OnPostUpdateCollection(PostCollectionUpdateEvent @event)
/// <summary>
/// Before deleting the element in collection
/// </summary>
/// <param name="event">Event parameters</param>
public virtual void OnPreRemoveCollection(PreCollectionRemoveEvent @event)
/// <summary>
/// Before recreating the element in collection
/// </summary>
/// <param name="event">Event parameters</param>
public virtual void OnPreRecreateCollection(PreCollectionRecreateEvent @event)
/// <summary>
/// Before refreshing the element in collection
/// </summary>
/// <param name="event">Event parameters</param>
public virtual void OnPreUpdateCollection(PreCollectionUpdateEvent @event)
/// <summary>
/// Before loading
/// </summary>
/// <param name="event"></param>
public virtual void OnPreLoad(PreLoadEvent @event)
You can also see the description of this extension point in ELMA API help.
Example of implementation
In our case, the implementation of IEntityEventsListener extension point is:
[Component]
public class EntityEvent : EntityEventsListener
{
//Remove invalid symbols from the contractor's phone number
public override void OnPostUpdateCollection (PostCollectionUpdateEvent @event)
{
var contr = @event.AffectedOwnerOrNull as IContractor;
if (contr != null)
{
var collection = @event.Collection;
var collectionEntry = @event.Session.PersistenceContext.GetCollectionEntry(@event.Collection);
var collectionEntries = collection.Entries(collectionEntry.LoadedPersister);
var listChars = new List<string> { " ", "-", "(", ")" };
var index = 1;
foreach (var entry in collectionEntries)
{
if (!(entry is IPhone))
continue;
var phone = entry as IPhone;
long number;
var lastNumber = phone.PhoneString;
if (lastNumber != null)
{
foreach (var str in listChars)
{
lastNumber = lastNumber.Replace(str, string.Empty);
}
}
if (!long.TryParse(lastNumber, out number))
throw new Exception(string.Format("Phone №{0} has invalid characters", index));
phone.PhoneString = lastNumber;
index++;
}
}
}
//Record the history of contractors' name changes
public override void OnPostUpdate (PostUpdateEvent @event)
{
if (!(@event.Entity is IContractor))
return;
var nameIndex = Array.IndexOf(@event.Persister.PropertyNames, "Name");
var contractor = @event.Entity as IContractor;
if ((string)@event.OldState[nameIndex] != (string)@event.State[nameIndex])
{
ContractorManager.Instance.AddComment(contractor, string.Format("{2} changed contractor's name from {0} to {1}\r\n", @event.OldState[nameIndex],
@event.State[nameIndex], contractor.ChangeAuthor.FullName));
}
}
//Record the history of adding/removing contractors' phone numbers
public override void OnPreUpdateCollection (PreCollectionUpdateEvent @event)
{
var contr = @event.AffectedOwnerOrNull as IContractor;
if (contr != null)
{
var collectionEntry = @event.Session.PersistenceContext.GetCollectionEntry(@event.Collection);
if (!collectionEntry.Role.Contains("Phone"))
return;
var newCollection = (@event.Collection as IEnumerable).Cast<object>().ToList(); //New collection
var oldCollection = (collectionEntry.Snapshot as IEnumerable).Cast<object>().ToList(); //Old collection
var removed = oldCollection.Except(newCollection).ToArray(); //Deleted elements from new collection
var added = newCollection.Except(oldCollection).ToArray(); //Added elements from new collection
if (collectionEntry.Role.Contains("Phone"))
{
foreach (var add in added)
{
var phone = add as IPhone;
if (phone != null)
{
ContractorManager.Instance.AddComment(contr, string.Format("Added phone number {0}\r\n", phone.PhoneString));
}
}
foreach (var rem in removed)
{
var phone = rem as IPhone;
if (phone != null)
{
ContractorManager.Instance.AddComment(contr, string.Format("Phone number {0} deleted\r\n", phone.PhoneString));
}
}
}
else
Logger.Log.Error("Collection's role does not end with Phone");
}
}
//Check if Annual Revenue is filled in. If not filled in - cancel the event
public override bool OnPreUpdate (PreUpdateEvent @event)
{
if (@event.Entity is IContractor)
{
try
{
const double zero = 0;
var nameIndex = Array.IndexOf(@event.Persister.PropertyNames, "AnnualIncome");
var annualIncomeValue = @event.State[nameIndex];
if (annualIncomeValue != null)
{
if (!annualIncomeValue.ToString().IsNullOrWhiteSpace() && (double)annualIncomeValue != zero && (double)annualIncomeValue > zero)
{
Logger.Log.Error("Annual Revenue value is filled in. Value = " + annualIncomeValue.ToString());
//Process the event
return false;
}
else
{
Logger.Log.Error("Annual Revenue value either <0 or =0 or =null");
return true; //Cancel the event
}
}
}
catch (Exception ex)
{
Logger.Log.Error(ex.Message);
return true; //Cancel the event
}
}
return false;
}
}
As a result, we will see the following: