public void Save(T eventSourced, IEvent incomingEvent) { string key = null; try { // Check if incoming event is duplicate if (this.inbox.ContainsKey(incomingEvent.EventId)) { // Incoming event is duplicate return; } var now = this.time.Now; var localNow = this.time.Now.ToLocalTime(); // No new events to persist if (eventSourced == null) { // Log the incoming message in the inbox this.inbox[incomingEvent.EventId] = this.inboxEntityFactory.Invoke(incomingEvent); return; } if (eventSourced.Id == default(Guid)) { throw new ArgumentOutOfRangeException("StreamId", $"The eventsourced of type {typeof(T).FullName} has a default GUID value for its stream id, which is not valid"); } // Log the incoming message in the inbox this.inbox[incomingEvent.EventId] = this.inboxEntityFactory.Invoke(incomingEvent); var pendingEvents = eventSourced.PendingEvents; if (pendingEvents.Count > 0) { var currentVersion = this.events.Any(e => e.Value.StreamId == eventSourced.Id) ? this.events .Where(e => e.Value.StreamId == eventSourced.Id) .Max(e => e.Value.Version) : 0; if (currentVersion + 1 != pendingEvents.First().Version) { throw new EventStoreConcurrencyException(); } // Cache Memento And Publish Stream var snapshot = ((ISnapshotOriginator)eventSourced).SaveToSnapshot(); key = eventSourced.Id.ToString(); // Cache in Sql Server var serializedMemento = this.serializer.Serialize(snapshot); var streamEntity = this.snapshots.TryGetValue(eventSourced.Id); if (streamEntity != null) { streamEntity.Version = eventSourced.Version; streamEntity.Payload = serializedMemento; streamEntity.UpdateLocalTime = localNow; } else { streamEntity = new SnapshotEntity { StreamType = this.streamType, StreamId = eventSourced.Id, Version = eventSourced.Version, Payload = serializedMemento, CreationLocalTime = localNow, UpdateLocalTime = localNow }; this.snapshots[eventSourced.Id] = streamEntity; } // Cache in memory this.cache.Set( key: key, value: new Tuple <ISnapshot, DateTime?>(snapshot, now), policy: new CacheItemPolicy { AbsoluteExpiration = this.time.OffSetNow.AddMinutes(30) }); List <EventEntity> eventEntities = new List <EventEntity>(); foreach (var @event in pendingEvents) { var e = (Message)@event; e.TransactionId = incomingEvent.TransactionId; e.EventId = this.guid.NewGuid(); e.StreamType = this.streamType; e.LocalTime = now; e.UtcTime = localNow; eventEntities.Add( new EventEntity { StreamType = this.streamType, StreamId = @event.StreamId, Version = @event.Version, EventId = @event.EventId, TransactionId = @event.TransactionId, EventType = @event.GetType().Name, CorrelationId = incomingEvent.EventId, LocalTime = localNow, UtcTime = now }); } lock (this.dbLock) { var eventCollectionBeforeCrash = this.eventCollectionVersion; try { for (int i = 0; i < pendingEvents.Count; i++) { this.eventCollectionVersion += 1; var @event = pendingEvents[i]; ((Message)@event).EventCollectionVersion = this.eventCollectionVersion; var entity = eventEntities[i]; entity.EventCollectionVersion = this.eventCollectionVersion; entity.Payload = this.serializer.Serialize(@event); this.events[this.eventCollectionVersion] = entity; } //var random = new Random(); //Thread.Sleep(random.Next(0, 1000)); this.CurrentEventCollectionVersion = this.eventCollectionVersion; } catch (Exception ex) { this.eventCollectionVersion = eventCollectionBeforeCrash; throw ex; } } } } catch (Exception ex) { this.log.Error(ex, $"An error ocurred while storing events while processing incoming event of type '{incomingEvent.GetType().Name}'"); // Mark cache as stale if (key != null) { var item = (Tuple <ISnapshot, DateTime?>) this.cache.Get(key); if (item != null && item.Item2.HasValue) { item = new Tuple <ISnapshot, DateTime?>(item.Item1, null); this.cache.Set( key, item, new CacheItemPolicy { AbsoluteExpiration = this.time.OffSetNow.AddMinutes(30) }); } } throw; } }
public long Save(T eventSourced, IEvent incomingEvent) { var pendingEvents = eventSourced.PendingEvents; if (pendingEvents.Count == 0) { return(this.eventCollectionVersion); } if (eventSourced.Id == default(Guid)) { throw new ArgumentOutOfRangeException("StreamId", $"The eventsourced of type {typeof(T).FullName} has a default GUID value for its stream id, which is not valid"); } var key = eventSourced.Id.ToString(); try { using (var context = this.contextFactory.Invoke(false)) { // Check if incoming event is duplicate if (this.IsDuplicate(incomingEvent.EventId, context)) { // Incoming event is duplicate return(this.eventCollectionVersion); } var currentVersion = context.Events.Any(e => e.StreamId == eventSourced.Id && e.StreamType == this.streamType) ? context.Events .Where(e => e.StreamId == eventSourced.Id && e.StreamType == this.streamType) .Max(e => e.Version) : 0; if (currentVersion + 1 != pendingEvents.First().Version) { throw new EventStoreConcurrencyException(); } var now = this.time.Now; var localNow = this.time.Now.ToLocalTime(); // Log the incoming message in the inbox var message = new InboxEntity { InboxStreamType = this.streamType, EventId = incomingEvent.EventId, TransactionId = incomingEvent.TransactionId, StreamType = incomingEvent.StreamType, StreamId = incomingEvent.StreamId, Version = incomingEvent.Version, EventType = incomingEvent.GetType().Name, EventCollectionVersion = incomingEvent.EventCollectionVersion, CreationLocalTime = localNow, Ignored = false, Payload = this.serializer.Serialize(incomingEvent) }; context.Inbox.Add(message); // Update subscription try { var subscription = context.Subscriptions.Where(s => s.StreamType == incomingEvent.StreamType && s.SubscriberStreamType == this.streamType).Single(); subscription.ProcessorBufferVersion = incomingEvent.ProcessorBufferVersion; subscription.UpdateLocalTime = localNow; } catch (Exception ex) { throw new InvalidOperationException($"The incoming event belongs to a an stream type of {incomingEvent.StreamType}, but the event store could not found a subscription for that stream type.", ex); } // Cache Memento And Publish Stream var snapshot = ((ISnapshotOriginator)eventSourced).SaveToSnapshot(); // Cache in Sql Server var serializedMemento = this.serializer.Serialize(snapshot); var streamEntity = context.Snapshots.Where(s => s.StreamId == eventSourced.Id && s.StreamType == this.streamType).SingleOrDefault(); if (streamEntity != null) { streamEntity.Version = eventSourced.Version; streamEntity.Payload = serializedMemento; streamEntity.UpdateLocalTime = localNow; } else { streamEntity = new SnapshotEntity { StreamType = this.streamType, StreamId = eventSourced.Id, Version = eventSourced.Version, Payload = serializedMemento, CreationLocalTime = localNow, UpdateLocalTime = localNow }; context.Snapshots.Add(streamEntity); } // Cache in memory this.cache.Set( key: key, value: new Tuple <ISnapshot, DateTime?>(snapshot, now), policy: new CacheItemPolicy { AbsoluteExpiration = this.time.OffSetNow.AddMinutes(30) }); // Denormalize if applicable. this.denormalizeIfApplicable(eventSourced, context); List <EventEntity> eventEntities = new List <EventEntity>(); foreach (var @event in pendingEvents) { var e = (Message)@event; e.TransactionId = incomingEvent.TransactionId; e.EventId = this.guid.NewGuid(); e.StreamType = this.streamType; eventEntities.Add( new EventEntity { StreamType = this.streamType, StreamId = @event.StreamId, Version = @event.Version, EventId = @event.EventId, TransactionId = @event.TransactionId, EventType = @event.GetType().Name, CorrelationId = incomingEvent.EventId, LocalTime = localNow, UtcTime = now }); } long eventCollectionVersionToPublish; lock (this) { var eventCollectionBeforeCrash = this.eventCollectionVersion; try { for (int i = 0; i < pendingEvents.Count; i++) { var ecv = Interlocked.Increment(ref this.eventCollectionVersion); var @event = pendingEvents[i]; ((Message)@event).EventCollectionVersion = ecv; var entity = eventEntities[i]; entity.EventCollectionVersion = ecv; entity.Payload = this.serializer.Serialize(@event); context.Events.Add(entity); } context.SaveChanges(); eventCollectionVersionToPublish = pendingEvents.Last().EventCollectionVersion; //return context.Events.Where(e => e.StreamType == this.streamType).Max(e => e.EventCollectionVersion); } catch (Exception ex) { this.eventCollectionVersion = eventCollectionBeforeCrash; throw ex; } } return(eventCollectionVersionToPublish); } } catch (Exception ex) { this.log.Error(ex, $"An error ocurred while storing events while processing incoming event of type '{incomingEvent.GetType().Name}'"); // Mark cache as stale var item = (Tuple <ISnapshot, DateTime?>) this.cache.Get(key); if (item != null && item.Item2.HasValue) { item = new Tuple <ISnapshot, DateTime?>(item.Item1, null); this.cache.Set( key, item, new CacheItemPolicy { AbsoluteExpiration = this.time.OffSetNow.AddMinutes(30) }); } throw; } }