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:

Links to API elements

EntityEventsListener

 

Attachments