/// <summary> /// Creates the current state of an entity from the event stream. /// </summary> /// <typeparam name="T">Type of the resulting entity</typeparam> /// <param name="stream">Event stream</param> /// <param name="resourceType">Resource type</param> /// <param name="id"></param> /// <returns>The resulting entity</returns> public static async Task <T> GetCurrentEntityAsync <T>(this IEventStream stream, ResourceType resourceType, int id) where T : class, new() { if (ResourceType.ResourceTypeDictionary.ContainsKey(resourceType.Name)) { var targetType = typeof(T); if (!targetType.IsAssignableFrom(resourceType.Type)) { throw new ArgumentException("The type parameter doesn't match up with the associated type of the ResourceType"); } var enumerator = stream.GetEnumerator(); var obj = default(T); while (await enumerator.MoveNextAsync()) { switch (enumerator.Current) { case CreatedEvent createdEv: if (createdEv.ResourceTypeName.Equals(resourceType.Name) && createdEv.Id == id) { obj = Activator.CreateInstance <T>(); } break; case PropertyChangedEvent propertyEv: if (!Equals(obj, default(T)) && Equals(propertyEv.ResourceTypeName, resourceType.Name) && propertyEv.Id == id) { var propertyInfo = targetType.GetProperty(propertyEv.PropertyName); propertyInfo.SetValue(obj, propertyEv.Value); } break; case DeletedEvent deletedEv: if (Equals(deletedEv.ResourceTypeName, resourceType.Name) && deletedEv.Id == id) { obj = default(T); // entity might be recreated later } break; } } return(obj); } else { throw new ArgumentException("A resource type with the given name does not exist"); } }
/// <summary> /// Walks through the specified event stream to obtain a summary of when the specified entity /// was created, modified and deleted. This is a potentially expensive operation. /// </summary> /// <remarks> /// This method only looks for standard CRUD events (<see cref="ICreateEvent"/>, <see cref="IUpdateEvent"/>, /// <see cref="IDeleteEvent"/>). If for your specific entity type there are other event types the semantically /// represent a change to the entity, these won't be part of the generated summary. /// /// In case the entity lived different lives (i.e. has been recreated after deletion), only the most /// recent "life" is considered for the history. Consider the following example event stream (with timestamps): /// create (12:00), update (12:04), delete (13:20), create (13:55), delete (14:00), create (16:10), update (16:25). /// In this case only the events from 16:10 and 16:25 will be considered and the property /// <see cref="HistorySummary.Deleted"/> will not be set. /// /// The event stream is assumed to be consistent. If the stream is inconsistent (e.g. has a create event /// immediately followed by another create event), the behavior and resulting summary is undefined. /// </remarks> public static async Task <HistorySummary> GetSummaryAsync(IEventStream eventStream, EntityId entityId) { var enumerator = eventStream.GetEnumerator(); var summary = new HistorySummary(); while (await enumerator.MoveNextAsync()) { if (enumerator.Current is ICrudEvent crudEvent && crudEvent.GetEntityType() == entityId.Type && crudEvent.Id == entityId.Id) { var timestamp = crudEvent.Timestamp; var user = (crudEvent as IUserActivityEvent)?.UserId; switch (crudEvent) { case ICreateEvent _: if (summary.Created.HasValue) { // assumption: entity was deleted before and is now recreated (we don't check if there // was a delete event before; it's not our job to validate the stream's consistency) summary = new HistorySummary(); } summary.Owner = user; summary.Created = timestamp; summary.LastModified = timestamp; summary.Changes.Add(new HistorySummary.Change(timestamp, "Created", user)); break; case IUpdateEvent _: summary.LastModified = timestamp; summary.Changes.Add(new HistorySummary.Change(timestamp, "Updated", user)); break; case IDeleteEvent _: summary.LastModified = timestamp; summary.Deleted = timestamp; summary.Changes.Add(new HistorySummary.Change(timestamp, "Deleted", user)); break; } } } return(summary); }
public IAsyncEnumerator <IEvent> GetExistingEvents() => _stream.GetEnumerator();