/// <summary>
        /// This will check against all of the processed entity/events (allEntities) to see if this entity already exists in
        /// event args that supersede the event args being passed in and if so returns true.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="eventDef"></param>
        /// <param name="allEntities"></param>
        /// <returns></returns>
        private static bool IsFiltered(
            IEntity entity,
            EventDefinitionTypeData eventDef,
            List <Tuple <IEntity, EventDefinitionTypeData> > allEntities)
        {
            var argType = eventDef.EventDefinition.Args.GetType();

            //check if the entity is found in any processed event data that could possible supersede this one
            var foundByEntity = allEntities
                                .Where(x => x.Item2.SupersedeAttributes.Length > 0
                                       //if it's the same arg type than it cannot supersede
                                       && x.Item2.EventArgType != argType &&
                                       Equals(x.Item1, entity))
                                .ToArray();

            //no args have been processed with this entity so it should not be filtered
            if (foundByEntity.Length == 0)
            {
                return(false);
            }

            if (argType.IsGenericType)
            {
                var supercededBy = foundByEntity
                                   .FirstOrDefault(x =>
                                                   x.Item2.SupersedeAttributes.Any(y =>
                                                                                   //if the attribute type is a generic type def then compare with the generic type def of the event arg
                                                                                   (y.SupersededEventArgsType.IsGenericTypeDefinition && y.SupersededEventArgsType == argType.GetGenericTypeDefinition())
                                                                                   //if the attribute type is not a generic type def then compare with the normal type of the event arg
                                                                                   || (y.SupersededEventArgsType.IsGenericTypeDefinition == false && y.SupersededEventArgsType == argType)));
                return(supercededBy != null);
            }
            else
            {
                var supercededBy = foundByEntity
                                   .FirstOrDefault(x =>
                                                   x.Item2.SupersedeAttributes.Any(y =>
                                                                                   //since the event arg type is not a generic type, then we just compare type 1:1
                                                                                   y.SupersededEventArgsType == argType));
                return(supercededBy != null);
            }
        }
        /// <summary>
        /// This will iterate over the events (latest first) and filter out any events or entities in event args that are included
        /// in more recent events that Supersede previous ones. For example, If an Entity has been Saved and then Deleted, we don't want
        /// to raise the Saved event (well actually we just don't want to include it in the args for that saved event)
        /// </summary>
        /// <param name="events"></param>
        /// <returns></returns>
        private static IEnumerable <IEventDefinition> FilterSupersededAndUpdateToLatestEntity(IReadOnlyList <IEventDefinition> events)
        {
            //used to keep the 'latest' entity and associated event definition data
            var allEntities = new List <Tuple <IEntity, EventDefinitionTypeData> >();

            //tracks all CancellableObjectEventArgs instances in the events which is the only type of args we can work with
            var cancelableArgs = new List <CancellableObjectEventArgs>();

            var result = new List <IEventDefinition>();

            //This will eagerly load all of the event arg types and their attributes so we don't have to continuously look this data up
            var allArgTypesWithAttributes = events.Select(x => x.Args.GetType())
                                            .Distinct()
                                            .ToDictionary(x => x, x => x.GetCustomAttributes <SupersedeEventAttribute>(false).ToArray());

            //Iterate all events and collect the actual entities in them and relates them to their corresponding EventDefinitionTypeData
            //we'll process the list in reverse because events are added in the order they are raised and we want to filter out
            //any entities from event args that are not longer relevant
            //(i.e. if an item is Deleted after it's Saved, we won't include the item in the Saved args)
            for (var index = events.Count - 1; index >= 0; index--)
            {
                var eventDefinition = events[index];

                var argType    = eventDefinition.Args.GetType();
                var attributes = allArgTypesWithAttributes[eventDefinition.Args.GetType()];

                var meta = new EventDefinitionTypeData
                {
                    EventDefinition     = eventDefinition,
                    EventArgType        = argType,
                    SupersedeAttributes = attributes
                };

                var args = eventDefinition.Args as CancellableObjectEventArgs;
                if (args != null)
                {
                    var list = TypeHelper.CreateGenericEnumerableFromObject(args.EventObject);

                    if (list == null)
                    {
                        //extract the event object
                        var obj = args.EventObject as IEntity;
                        if (obj != null)
                        {
                            //Now check if this entity already exists in other event args that supersede this current event arg type
                            if (IsFiltered(obj, meta, allEntities) == false)
                            {
                                //if it's not filtered we can adde these args to the response
                                cancelableArgs.Add(args);
                                result.Add(eventDefinition);
                                //track the entity
                                allEntities.Add(Tuple.Create(obj, meta));
                            }
                        }
                        else
                        {
                            //Can't retrieve the entity so cant' filter or inspect, just add to the output
                            result.Add(eventDefinition);
                        }
                    }
                    else
                    {
                        var toRemove = new List <IEntity>();
                        foreach (var entity in list)
                        {
                            //extract the event object
                            var obj = entity as IEntity;
                            if (obj != null)
                            {
                                //Now check if this entity already exists in other event args that supersede this current event arg type
                                if (IsFiltered(obj, meta, allEntities))
                                {
                                    //track it to be removed
                                    toRemove.Add(obj);
                                }
                                else
                                {
                                    //track the entity, it's not filtered
                                    allEntities.Add(Tuple.Create(obj, meta));
                                }
                            }
                            else
                            {
                                //we don't need to do anything here, we can't cast to IEntity so we cannot filter, so it will just remain in the list
                            }
                        }

                        //remove anything that has been filtered
                        foreach (var entity in toRemove)
                        {
                            list.Remove(entity);
                        }

                        //track the event and include in the response if there's still entities remaining in the list
                        if (list.Count > 0)
                        {
                            if (toRemove.Count > 0)
                            {
                                //re-assign if the items have changed
                                args.EventObject = list;
                            }
                            cancelableArgs.Add(args);
                            result.Add(eventDefinition);
                        }
                    }
                }
                else
                {
                    //it's not a cancelable event arg so we just include it in the result
                    result.Add(eventDefinition);
                }
            }

            //Now we'll deal with ensuring that only the latest(non stale) entities are used throughout all event args
            UpdateToLatestEntities(allEntities, cancelableArgs);

            //we need to reverse the result since we've been adding by latest added events first!
            result.Reverse();

            return(result);
        }