public void Aggregate_entity_has_index_with_AggregateId() { var context = new EventStoreDbContext(_dbContextOptions); IEntityType sut = context.Model.FindEntityType(typeof(Aggregate)); IIndex actual = sut.FindIndex(sut.FindProperty("AggregateId")); actual.Should().NotBeNull(); actual.IsUnique.Should().BeTrue(); }
public void TestCleanup() { using (EventStoreDbContext db = CreateDbContext()) { db.Database.ExecuteSqlCommand("DELETE FROM Aggregates"); db.Database.ExecuteSqlCommand("DELETE FROM PersistentEvents"); db.Database.ExecuteSqlCommand("DELETE FROM PendingEvents"); db.Database.ExecuteSqlCommand("DELETE FROM UniqueIndexedProperties"); } }
private async Task UpdateUniqueIndexedProperties <T>( EventStoreDbContext context, Guid sourceId, List <IDomainEvent> events, CancellationToken cancellationToken) where T : class, IEventSourced { Dictionary <string, UniqueIndexedProperty> restored = await context .UniqueIndexedProperties .Where( p => p.AggregateType == typeof(T).FullName && p.AggregateId == sourceId) .ToDictionaryAsync(p => p.PropertyName, cancellationToken) .ConfigureAwait(false); var properties = new List <UniqueIndexedProperty>(); foreach (var indexedEvent in events.OfType <IUniqueIndexedDomainEvent>()) { foreach (string name in indexedEvent.UniqueIndexedProperties.Keys) { UniqueIndexedProperty property; if (restored.TryGetValue(name, out property)) { context.UniqueIndexedProperties.Remove(property); } property = properties.Find(p => p.PropertyName == name); if (property != null) { properties.Remove(property); } string value = indexedEvent.UniqueIndexedProperties[name]; if (value == null) { continue; } property = new UniqueIndexedProperty { AggregateType = typeof(T).FullName, PropertyName = name, PropertyValue = value, AggregateId = sourceId, Version = indexedEvent.Version }; properties.Add(property); } } context.UniqueIndexedProperties.AddRange(properties); }
private static Task <List <PendingEvent> > LoadEvents( EventStoreDbContext context, Guid sourceId, CancellationToken cancellationToken) { IQueryable <PendingEvent> query = from e in context.PendingEvents where e.AggregateId == sourceId orderby e.Version select e; return(query.ToListAsync(cancellationToken)); }
private void InsertEvent( EventStoreDbContext context, IDomainEvent domainEvent, string operationId, Guid?correlationId, string contributor) { var envelope = new Envelope(Guid.NewGuid(), domainEvent, operationId, correlationId, contributor); context.PersistentEvents.Add(PersistentEvent.FromEnvelope(envelope, _serializer)); context.PendingEvents.Add(PendingEvent.FromEnvelope(envelope, _serializer)); }
private void InsertEvents( EventStoreDbContext context, List <IDomainEvent> events, string operationId, Guid?correlationId, string contributor) { foreach (IDomainEvent domainEvent in events) { InsertEvent(context, domainEvent, operationId, correlationId, contributor); } }
private void InsertEvents <T>( EventStoreDbContext context, List <IDomainEvent> events, string operationId, Guid?correlationId, string contributor) where T : class, IEventSourced { foreach (IDomainEvent domainEvent in events) { InsertEvent <T>(context, domainEvent, operationId, correlationId, contributor); } }
public void PersistentEvent_entity_has_index_with_AggregateId_Version() { var context = new EventStoreDbContext(_dbContextOptions); IEntityType sut = context.Model.FindEntityType(typeof(PersistentEvent)); IIndex actual = sut.FindIndex(new[] { sut.FindProperty("AggregateId"), sut.FindProperty("Version"), }); actual.Should().NotBeNull(); actual.IsUnique.Should().BeTrue(); }
public void PendingEvent_entity_has_primary_key_with_AggregateId_Version() { var context = new EventStoreDbContext(_dbContextOptions); IEntityType sut = context.Model.FindEntityType(typeof(PendingEvent)); IKey actual = sut.FindPrimaryKey(); actual.Should().NotBeNull(); actual.Properties.Should().Equal(new[] { sut.FindProperty("AggregateId"), sut.FindProperty("Version"), }); }
public void UniqueIndexedProperty_entity_has_index_with_AggregateId_PropertyName() { var context = new EventStoreDbContext(_dbContextOptions); IEntityType sut = context.Model.FindEntityType(typeof(UniqueIndexedProperty)); IIndex actual = sut.FindIndex(new[] { sut.FindProperty("AggregateType"), sut.FindProperty("AggregateId"), sut.FindProperty("PropertyName"), }); actual.Should().NotBeNull(); actual.IsUnique.Should().BeTrue(); }
public void UniqueIndexedProperty_entity_has_primary_key_with_AggregateType_PropertyName_PropertyValue() { var context = new EventStoreDbContext(_dbContextOptions); IEntityType sut = context.Model.FindEntityType(typeof(UniqueIndexedProperty)); IKey actual = sut.FindPrimaryKey(); actual.Should().NotBeNull(); actual.Properties.Should().Equal(new[] { sut.FindProperty("AggregateType"), sut.FindProperty("PropertyName"), sut.FindProperty("PropertyValue"), }); }
public void Correlation_entity_has_primary_key_with_AggregateType_AggregateId_and_CorrelationId() { var context = new EventStoreDbContext(_dbContextOptions); IEntityType sut = context.Model.FindEntityType(typeof(Correlation)); IKey actual = sut.FindPrimaryKey(); actual.Should().NotBeNull(); actual.Properties.Should().Equal(new[] { sut.FindProperty("AggregateType"), sut.FindProperty("AggregateId"), sut.FindProperty("CorrelationId"), }); }
private void InsertEvents( EventStoreDbContext context, List <IDomainEvent> events, Guid?correlationId) { foreach (IDomainEvent domainEvent in events) { var envelope = correlationId == null ? new Envelope(domainEvent) : new Envelope(correlationId.Value, domainEvent); context.PersistentEvents.Add(PersistentEvent.FromEnvelope(envelope, _serializer)); context.PendingEvents.Add(PendingEvent.FromEnvelope(envelope, _serializer)); } }
private async Task RunFlushPendingEvents( Guid sourceId, CancellationToken cancellationToken) { using (EventStoreDbContext context = _dbContextFactory.Invoke()) { List <PendingEvent> pendingEvents = await LoadEvents(context, sourceId, cancellationToken).ConfigureAwait(false); if (pendingEvents.Any()) { await SendEvents(pendingEvents, cancellationToken).ConfigureAwait(false); await RemoveEvents(context, pendingEvents, cancellationToken).ConfigureAwait(false); } } }
public async Task PublishAllPendingEvents(CancellationToken cancellationToken) { using (EventStoreDbContext context = _dbContextFactory.Invoke()) { foreach (Guid sourceId in await context .PendingEvents .Select(e => e.AggregateId) .Distinct() .ToListAsync(cancellationToken) .ConfigureAwait(false)) { await PublishEvents(sourceId, cancellationToken).ConfigureAwait(false); } } }
private void InsertCorrelation( Guid sourceId, Guid?correlationId, EventStoreDbContext context) { if (correlationId == null) { return; } context.Correlations.Add(new Correlation { AggregateId = sourceId, CorrelationId = correlationId.Value, HandledAt = DateTimeOffset.Now }); }
private static async Task RemoveEvents( EventStoreDbContext context, List <PendingEvent> pendingEvents, CancellationToken cancellationToken) { foreach (PendingEvent pendingEvent in pendingEvents) { try { context.PendingEvents.Remove(pendingEvent); await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } catch (DbUpdateConcurrencyException) { context.Entry(pendingEvent).State = EntityState.Detached; } } }
public async Task FlushPendingEvents_absorbs_exception_caused_by_that_some_pending_event_already_deleted_since_loaded() { // Arrange var completionSource = new TaskCompletionSource <bool>(); IMessageBus messageBus = new AwaitingMessageBus(completionSource.Task); var fixture = new Fixture(); FakeUser user = fixture.Create <FakeUser>(); user.ChangeUsername(fixture.Create(nameof(user.Username))); var eventStore = new SqlEventStore(CreateDbContext, _serializer); await eventStore.SaveEvents <FakeUser>(user.FlushPendingEvents()); var sut = new SqlEventPublisher(CreateDbContext, _serializer, messageBus); // Act Func <Task> action = async() => { Task flushTask = sut.FlushPendingEvents(user.Id, CancellationToken.None); using (EventStoreDbContext db = CreateDbContext()) { List <PendingEvent> pendingEvents = await db .PendingEvents .Where(e => e.AggregateId == user.Id) .OrderBy(e => e.Version) .Take(1) .ToListAsync(); db.PendingEvents.RemoveRange(pendingEvents); await db.SaveChangesAsync(); } completionSource.SetResult(true); await flushTask; }; // Assert action.ShouldNotThrow(); using (EventStoreDbContext db = CreateDbContext()) { (await db.PendingEvents.AnyAsync(e => e.AggregateId == user.Id)) .Should().BeFalse("all pending events should be deleted"); } }
private async Task Save <T>( Guid sourceId, List <IDomainEvent> events, Guid?correlationId, CancellationToken cancellationToken) where T : class, IEventSourced { using (EventStoreDbContext context = _dbContextFactory.Invoke()) { await UpsertAggregate <T>(context, sourceId, events, cancellationToken).ConfigureAwait(false); InsertEvents(context, events, correlationId); await UpdateUniqueIndexedProperties <T>(context, sourceId, events, cancellationToken).ConfigureAwait(false); InsertCorrelation(sourceId, correlationId, context); await SaveChanges <T>(context, sourceId, cancellationToken); } }
private void InsertCorrelation <T>( Guid sourceId, Guid?correlationId, EventStoreDbContext context) where T : class, IEventSourced { if (correlationId == null) { return; } context.Correlations.Add(new Correlation { AggregateType = typeof(T).FullName, AggregateId = sourceId, CorrelationId = correlationId.Value, HandledAt = DateTime.UtcNow, }); }
public SqlEventStore_features(ITestOutputHelper output) { this.output = output; fixture = new Fixture().Customize(new AutoMoqCustomization()); fixture.Inject <Func <EventStoreDbContext> >(() => new DataContext()); userId = Guid.NewGuid(); serializer = new JsonMessageSerializer(); fixture.Inject(serializer); sut = fixture.Create <SqlEventStore>(); mockDbContext = Mock.Of <EventStoreDbContext>( x => x.SaveChangesAsync() == Task.FromResult(default(int))); mockDbContext.Aggregates = Mock.Of <DbSet <Aggregate> >(); Mock.Get(mockDbContext.Aggregates).SetupData(); mockDbContext.PersistentEvents = Mock.Of <DbSet <PersistentEvent> >(); Mock.Get(mockDbContext.PersistentEvents).SetupData(); mockDbContext.PendingEvents = Mock.Of <DbSet <PendingEvent> >(); Mock.Get(mockDbContext.PendingEvents).SetupData(); mockDbContext.UniqueIndexedProperties = Mock.Of <DbSet <UniqueIndexedProperty> >(); Mock.Get(mockDbContext.UniqueIndexedProperties).SetupData(); using (var db = new DataContext()) { db.Database.Log = output.WriteLine; db.Database.ExecuteSqlCommand("DELETE FROM Aggregates"); db.Database.ExecuteSqlCommand("DELETE FROM PersistentEvents"); db.Database.ExecuteSqlCommand("DELETE FROM PendingEvents"); db.Database.ExecuteSqlCommand("DELETE FROM UniqueIndexedProperties"); } }
private async Task <Guid?> FindIdByProperty <T>( string name, string value, CancellationToken cancellationToken) where T : class, IEventSourced { using (EventStoreDbContext context = _dbContextFactory.Invoke()) { IQueryable <UniqueIndexedProperty> query = from p in context.UniqueIndexedProperties where p.AggregateType == typeof(T).FullName && p.PropertyName == name && p.PropertyValue == value select p; UniqueIndexedProperty property = await query .SingleOrDefaultAsync(cancellationToken) .ConfigureAwait(false); return(property?.AggregateId); } }
private static async Task RemoveEvents( EventStoreDbContext context, List <PendingEvent> pendingEvents, CancellationToken cancellationToken) { foreach (PendingEvent pendingEvent in pendingEvents) { try { context.PendingEvents.Remove(pendingEvent); await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } #if NETSTANDARD2_0 catch (DbUpdateConcurrencyException) #else catch (DbUpdateConcurrencyException exception) when(exception.InnerException is OptimisticConcurrencyException) #endif { context.Entry(pendingEvent).State = EntityState.Detached; } } }
public void TestInitialize() { _fixture = new Fixture().Customize(new AutoMoqCustomization()); _fixture.Inject <Func <EventStoreDbContext> >(() => new DataContext()); _userId = Guid.NewGuid(); _serializer = new JsonMessageSerializer(); _fixture.Inject(_serializer); _sut = _fixture.Create <SqlEventStore>(); _mockDbContext = Mock.Of <EventStoreDbContext>( x => x.SaveChangesAsync() == Task.FromResult(default(int))); _mockDbContext.Aggregates = Mock.Of <DbSet <Aggregate> >(); Mock.Get(_mockDbContext.Aggregates).SetupData(); _mockDbContext.PersistentEvents = Mock.Of <DbSet <PersistentEvent> >(); Mock.Get(_mockDbContext.PersistentEvents).SetupData(); _mockDbContext.PendingEvents = Mock.Of <DbSet <PendingEvent> >(); Mock.Get(_mockDbContext.PendingEvents).SetupData(); _mockDbContext.UniqueIndexedProperties = Mock.Of <DbSet <UniqueIndexedProperty> >(); Mock.Get(_mockDbContext.UniqueIndexedProperties).SetupData(); using (var db = new DataContext()) { db.Database.Log = m => TestContext?.WriteLine(m); db.Database.ExecuteSqlCommand("DELETE FROM Aggregates"); db.Database.ExecuteSqlCommand("DELETE FROM PersistentEvents"); db.Database.ExecuteSqlCommand("DELETE FROM PendingEvents"); db.Database.ExecuteSqlCommand("DELETE FROM UniqueIndexedProperties"); } }
public async Task FlushAllPendingEvents(CancellationToken cancellationToken) { using (EventStoreDbContext context = _dbContextFactory.Invoke()) { Loop: IEnumerable <Guid> source = await context .PendingEvents .OrderBy(e => e.AggregateId) .Select(e => e.AggregateId) .Take(1000) .Distinct() .ToListAsync(cancellationToken) .ConfigureAwait(false); Task[] tasks = source.Select(sourceId => FlushPendingEvents(sourceId, cancellationToken)).ToArray(); await Task.WhenAll(tasks).ConfigureAwait(false); if (source.Any()) { goto Loop; } } }
private async Task <IEnumerable <IDomainEvent> > Load( Guid sourceId, int afterVersion, CancellationToken cancellationToken) { using (EventStoreDbContext context = _dbContextFactory.Invoke()) { List <PersistentEvent> events = await context .PersistentEvents .Where(e => e.AggregateId == sourceId) .Where(e => e.Version > afterVersion) .OrderBy(e => e.Version) .ToListAsync(cancellationToken) .ConfigureAwait(false); List <IDomainEvent> domainEvents = events .Select(e => e.EventJson) .Select(_serializer.Deserialize) .Cast <IDomainEvent>() .ToList(); return(domainEvents); } }
private async Task SaveChanges <T>( EventStoreDbContext context, Guid sourceId, Guid?correlationId, CancellationToken cancellationToken) where T : class, IEventSourced { #if NETSTANDARD2_0 try { using (IDbContextTransaction transaction = await context.Database.BeginTransactionAsync(cancellationToken).ConfigureAwait(false)) { await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); transaction.Commit(); } } catch (DbUpdateException exception) when(correlationId.HasValue) { IQueryable <Correlation> query = from c in context.Correlations where c.AggregateId == sourceId && c.CorrelationId == correlationId select c; if (await query.AnyAsync().ConfigureAwait(false)) { throw new DuplicateCorrelationException( typeof(T), sourceId, correlationId.Value, exception); } throw; } #else try { await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); } catch (DbUpdateException exception) when(exception.InnerException is UpdateException) { var updateException = (UpdateException)exception.InnerException; Correlation correlation = updateException .StateEntries .Select(s => s.Entity) .OfType <Correlation>() .FirstOrDefault(); if (correlation != null) { throw new DuplicateCorrelationException( typeof(T), sourceId, correlation.CorrelationId, exception); } throw; } #endif }