コード例 #1
0
 private void CleanDatabases()
 {
     using (var ctx = new EventStoreDbContext(GetDbOptions()))
     {
         ctx.RemoveRange(ctx.Set <Event>());
         ctx.RemoveRange(ctx.Set <Snapshot>());
         ctx.SaveChanges();
     }
 }
コード例 #2
0
        private async Task <long> ComputeEventSequence(EventStoreDbContext ctx, bool useBuffer, int hashedAggregateId)
        {
            long currentSequence = 0;

            if (_bufferInfo?.UseBuffer == true && useBuffer)
            {
                currentSequence = s_Events.Max(e => (long?)e.Sequence) ?? 0;
            }
            if (currentSequence == 0)
            {
                var currentInMemoryEvents = ExtractEventsFromChangeTracker(ctx);
                if (currentInMemoryEvents.Count > 0) // We are currently storing a range of events, also a context is dedicated to one aggId
                {
                    currentSequence = Convert.ToInt64(currentInMemoryEvents.Max(e => e.Sequence));
                }
                else
                {
                    currentSequence = await ctx
                                      .Set <Event>()
                                      .AsNoTracking()
                                      .Where(t => t.HashedAggregateId == hashedAggregateId)
                                      .MaxAsync(e => (long?)e.Sequence)
                                      .ConfigureAwait(false) ?? 0;
                }
            }
            return(++currentSequence);
        }
コード例 #3
0
        private async Task <AggregateState> GetRehydratedAggregateStateAsync(
            object aggregateId,
            Type aggregateType,
            EventStoreDbContext externalCtx = null)
        {
            List <IDomainEvent> events = new List <IDomainEvent>();

#if NETSTANDARD2_0
            events = await
                     GetAllEventsByAggregateId(aggregateType, aggregateId)
                     .ToList().ConfigureAwait(false);
#elif NETSTANDARD2_1
            await foreach (var @event in GetAllEventsByAggregateId(aggregateType, aggregateId))
            {
                events.Add(@event);
            }
#endif
            if (externalCtx != null)
            {
                var eventsInChangeTracker = ExtractEventsFromChangeTracker(externalCtx).Select(GetRehydratedEventFromDbEvent);
                events = events.Concat(eventsInChangeTracker).OrderBy(s => s.Sequence).ToList();
            }
            Snapshot snapshot = null;
            using (var ctx = new EventStoreDbContext(_dbContextOptions, _archiveBehavior))
            {
                var hashedAggregateId = aggregateId.ToJson(true).GetHashCode();
                snapshot = await ctx.Set <Snapshot>()
                           .Where(t => t.AggregateType == aggregateType.AssemblyQualifiedName && t.HashedAggregateId == hashedAggregateId)
                           .FirstOrDefaultAsync().ConfigureAwait(false);
            }

            PropertyInfo   stateProp  = aggregateType.GetAllProperties().FirstOrDefault(p => p.PropertyType.IsSubclassOf(typeof(AggregateState)));
            FieldInfo      stateField = aggregateType.GetAllFields().FirstOrDefault(f => f.FieldType.IsSubclassOf(typeof(AggregateState)));
            Type           stateType  = stateProp?.PropertyType ?? stateField?.FieldType;
            AggregateState state      = null;
            if (stateType != null)
            {
                if (snapshot != null)
                {
                    state = snapshot.SnapshotData.FromJson(stateType) as AggregateState;
                }
                else
                {
                    state = stateType.CreateInstance() as AggregateState;
                }
            }
            else
            {
                throw new InvalidOperationException("EFEventStore.GetRehydratedAggregateAsync() : Cannot find property/field that manage state for aggregate" +
                                                    $" type {aggregateType.FullName}. State should be a property or a field of the aggregate");
            }

            state.ApplyRange(events);
            return(state);
        }
コード例 #4
0
        private async Task ManageSnapshotBehavior(
            IDomainEvent @event,
            EventStoreDbContext ctx,
            int hashedAggregateId)
        {
            bool IsSnaphostEnabled()
            {
                return(_snapshotBehaviorProvider != null &&
                       _archiveBehavior != SnapshotEventsArchiveBehavior.Disabled);
            }

            if (@event.AggregateId != null && @event.AggregateType != null)
            {
                var evtType = @event.GetType();
                if (IsSnaphostEnabled())
                {
                    var behavior = _snapshotBehaviorProvider !.GetBehaviorForEventType(evtType);
                    if (behavior?.IsSnapshotNeeded(@event) == true)
                    {
                        var aggState = await GetRehydratedAggregateStateAsync(@event.AggregateId, @event.AggregateType, ctx)
                                       .ConfigureAwait(false);

                        var eventsToArchive = behavior.GenerateSnapshot(aggState);

                        if (eventsToArchive?.Any() == true)
                        {
                            if (aggState != null)
                            {
                                var previousSnapshot = await ctx
                                                       .Set <Snapshot>()
                                                       .FirstOrDefaultAsync(s =>
                                                                            s.HashedAggregateId == hashedAggregateId &&
                                                                            s.AggregateType == @event.AggregateType.AssemblyQualifiedName)
                                                       .ConfigureAwait(false);

                                if (previousSnapshot != null)
                                {
                                    ctx.Remove(previousSnapshot);
                                }
                                ctx.Add(new Snapshot
                                {
                                    AggregateType        = @event.AggregateType.AssemblyQualifiedName,
                                    HashedAggregateId    = hashedAggregateId,
                                    SnapshotBehaviorType = behavior.GetType().AssemblyQualifiedName,
                                    SnapshotTime         = DateTime.Now,
                                    SnapshotData         = aggState.ToJson(new AggregateStateSerialisationContract())
                                });
                            }
                            await StoreArchiveEventsAsync(eventsToArchive, ctx).ConfigureAwait(false);
                        }
                    }
                }
            }
        }
コード例 #5
0
 public IAsyncEnumerable <IDomainEvent> GetAllEventsByEventType(Type eventType)
 {
     using (var ctx = new EventStoreDbContext(_dbContextOptions))
     {
         var dbEvents = ctx
                        .Set <Event>()
                        .AsNoTracking()
                        .Where(c => c.EventType == eventType.AssemblyQualifiedName)
                        .ToList();
         return(dbEvents.Select(GetRehydratedEventFromDbEvent).ToAsyncEnumerable());
     }
 }
コード例 #6
0
        private async Task StoreArchiveEventsAsync(IEnumerable <IDomainEvent> archiveEvents, EventStoreDbContext externalCtx)
        {
            switch (_archiveBehavior)
            {
            case SnapshotEventsArchiveBehavior.StoreToNewTable:
                using (var ctx = new EventStoreDbContext(_dbContextOptions))
                {
                    ctx.AddRange(archiveEvents.Select(GetArchiveEventFromIDomainEvent).ToList());
                    await ctx.SaveChangesAsync().ConfigureAwait(false);
                }
                break;

            case SnapshotEventsArchiveBehavior.StoreToNewDatabase:
                if (_archiveBehaviorDbContextOptions != null)
                {
                    using (var ctx = new ArchiveEventStoreDbContext(_archiveBehaviorDbContextOptions))
                    {
                        ctx.AddRange(
                            archiveEvents.Select(GetArchiveEventFromIDomainEvent));
                        await ctx.SaveChangesAsync().ConfigureAwait(false);
                    }
                }
                else
                {
                    throw new InvalidOperationException("EFEventStore.StoreDomainEventAsync() : Snapshot will be created and event to archive cannot be saved because DbContextOptions for archive database weren't provided during configuration on Bootstrapper. Either specify DbContextOptions for event's archive database or change archive behavior");
                }
                break;
            }
            var eventsInContext = externalCtx
                                  .ChangeTracker
                                  .Entries()
                                  .Where(e => e.State != EntityState.Detached && e.State != EntityState.Deleted)
                                  .Select(e => e.Entity as Event)
                                  .WhereNotNull()
                                  .Where(e => archiveEvents.Any(ev => ev.Id == e !.Id))
                                  .ToList();

            if (eventsInContext.Count > 0)
            {
                eventsInContext.DoForEach(e => externalCtx.Entry(e).State = EntityState.Detached);
            }
            using (var ctx = new EventStoreDbContext(_dbContextOptions))
            {
                var archiveEventsIds = archiveEvents.Select(e => e.Id).ToList();
                var events           = await ctx.Set <Event>().Where(e => archiveEventsIds.Contains(e.Id)).ToListAsync().ConfigureAwait(false);

                if (events.Count > 0)
                {
                    ctx.RemoveRange(events);
                    await ctx.SaveChangesAsync().ConfigureAwait(false);
                }
            }
        }
コード例 #7
0
 public IAsyncEnumerable <T> GetAllEventsByEventType <T>()
     where T : class, IDomainEvent
 {
     using (var ctx = new EventStoreDbContext(_dbContextOptions))
     {
         var dbEvents = ctx
                        .Set <Event>()
                        .AsNoTracking()
                        .Where(c => c.EventType == typeof(T).AssemblyQualifiedName)
                        .ToList();
         return(dbEvents.Select(e => GetRehydratedEventFromDbEvent(e) as T).WhereNotNull().ToAsyncEnumerable());
     }
 }
コード例 #8
0
 public IAsyncEnumerable <IDomainEvent> GetAllEventsByAggregateId(Type aggregateType, object aggregateId)
 {
     using (var ctx = new EventStoreDbContext(_dbContextOptions))
     {
         var dbEvents = ctx
                        .Set <Event>()
                        .AsNoTracking()
                        .Where(c => c.AggregateType == aggregateType.AssemblyQualifiedName &&
                               c.AggregateIdType == aggregateId.GetType().AssemblyQualifiedName &&
                               c.HashedAggregateId == aggregateId.ToJson(true).GetHashCode())
                        .ToList();
         return(dbEvents.Select(GetRehydratedEventFromDbEvent).ToAsyncEnumerable());
     }
 }
コード例 #9
0
 public async IAsyncEnumerable <IDomainEvent> GetAllEventsByAggregateType(Type aggregateType)
 {
     using (var ctx = new EventStoreDbContext(_dbContextOptions))
     {
         var dbEvents = ctx
                        .Set <Event>()
                        .AsNoTracking()
                        .Where(c => c.AggregateType == aggregateType.AssemblyQualifiedName)
                        .AsAsyncEnumerable();
         await foreach (var @event in dbEvents)
         {
             yield return(GetRehydratedEventFromDbEvent(@event));
         }
     }
 }
コード例 #10
0
        private async Task StoreArchiveEventsAsync(IEnumerable <IDomainEvent> archiveEvents, EventStoreDbContext externalCtx)
        {
            switch (_archiveBehavior)
            {
            case SnapshotEventsArchiveBehavior.StoreToNewTable:
                using (var ctx = new EventStoreDbContext(_dbContextOptions))
                {
                    ctx.AddRange(archiveEvents.Select(GetArchiveEventFromIDomainEvent).ToList());
                    await ctx.SaveChangesAsync().ConfigureAwait(false);
                }
                break;

            case SnapshotEventsArchiveBehavior.StoreToNewDatabase:
                using (var ctx = new ArchiveEventStoreDbContext(_archiveBehaviorDbContextOptions))
                {
                    ctx.AddRange(
                        archiveEvents.Select(GetArchiveEventFromIDomainEvent));
                    await ctx.SaveChangesAsync().ConfigureAwait(false);
                }
                break;
            }
            var eventsInContext = externalCtx
                                  .ChangeTracker
                                  .Entries()
                                  .Where(e => e.State != EntityState.Detached && e.State != EntityState.Deleted)
                                  .Select(e => e.Entity as Event)
                                  .WhereNotNull()
                                  .Where(e => archiveEvents.Any(ev => ev.Id == e.Id))
                                  .ToList();

            if (eventsInContext.Count > 0)
            {
                eventsInContext.DoForEach(e => externalCtx.Entry(e).State = EntityState.Detached);
            }
            using (var ctx = new EventStoreDbContext(_dbContextOptions))
            {
                var archiveEventsIds = archiveEvents.Select(e => e.Id).ToList();
                var events           = await ctx.Set <Event>().Where(e => archiveEventsIds.Contains(e.Id)).ToListAsync().ConfigureAwait(false);

                if (events.Count > 0)
                {
                    ctx.RemoveRange(events);
                    await ctx.SaveChangesAsync().ConfigureAwait(false);
                }
            }
        }
コード例 #11
0
 public async IAsyncEnumerable <T> GetAllEventsByEventType <T>()
     where T : class, IDomainEvent
 {
     using (var ctx = new EventStoreDbContext(_dbContextOptions))
     {
         var dbEvents = ctx
                        .Set <Event>()
                        .AsNoTracking()
                        .Where(c => c.EventType == typeof(T).AssemblyQualifiedName)
                        .AsAsyncEnumerable();
         await foreach (var @event in dbEvents)
         {
             if (GetRehydratedEventFromDbEvent(@event) is T rehydratedEvent)
             {
                 yield return(rehydratedEvent);
             }
         }
     }
 }
コード例 #12
0
        public async Task <IEventSourcedAggregate> GetRehydratedAggregateAsync(
            object aggregateUniqueId, Type aggregateType)
        {
            if (aggregateUniqueId == null)
            {
                throw new ArgumentNullException(nameof(aggregateUniqueId));
            }
            var aggInstance = aggregateType.CreateInstance() as IEventSourcedAggregate;

            if (aggInstance == null)
            {
                throw new InvalidOperationException("EFEventStore.GetRehydratedAggregateAsync() : Cannot create a new instance of" +
                                                    $" {aggregateType.FullName} aggregate. It should have one parameterless constructor (can be private).");
            }
            Snapshot snapshot = null;

            using (var ctx = new EventStoreDbContext(_dbContextOptions, _archiveBehavior))
            {
                var hashedAggregateId = aggregateUniqueId.ToJson(true).GetHashCode();
                snapshot = await ctx.Set <Snapshot>()
                           .Where(t => t.AggregateType == aggregateType.AssemblyQualifiedName && t.HashedAggregateId == hashedAggregateId)
                           .FirstOrDefaultAsync().ConfigureAwait(false);
            }
            PropertyInfo stateProp  = aggregateType.GetAllProperties().FirstOrDefault(p => p.PropertyType.IsSubclassOf(typeof(AggregateState)));
            FieldInfo    stateField = aggregateType.GetAllFields().FirstOrDefault(f => f.FieldType.IsSubclassOf(typeof(AggregateState)));
            Type         stateType  = stateProp?.PropertyType ?? stateField?.FieldType;

            if (stateType != null)
            {
                AggregateState state = stateType.CreateInstance() as AggregateState;
                if (snapshot != null)
                {
                    state = snapshot.SnapshotData.FromJson(stateType) as AggregateState;
                    if (state == null)
                    {
                        throw new InvalidOperationException("EFEventStore.GetRehydratedAggregateAsync() : Cannot retrieve a valid state from snapshot in database.");
                    }
                }
                if (stateProp != null)
                {
                    stateProp.SetValue(aggInstance, state);
                }
                else
                {
                    stateField.SetValue(aggInstance, state);
                }
            }
            else
            {
                throw new InvalidOperationException("EFEventStore.GetRehydratedAggregateAsync() : Cannot find property/field that manage state for aggregate" +
                                                    $" type {aggregateType.FullName}. State should be a property or a field of the aggregate");
            }
            List <IDomainEvent> events = new List <IDomainEvent>();

#if NETSTANDARD2_0
            events = await
                     GetAllEventsByAggregateId(aggregateType, aggregateUniqueId)
                     .ToList().ConfigureAwait(false);
#elif NETSTANDARD2_1
            await foreach (var @event in GetAllEventsByAggregateId(aggregateType, aggregateUniqueId))
            {
                events.Add(@event);
            }
#endif
            aggInstance.RehydrateState(events);
            return(aggInstance);
        }