/// <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); }