From 07aa7431851ed2eb3424e5c629d91f42c5b14230 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Thu, 6 Jul 2023 16:11:48 +0200 Subject: [PATCH 1/3] Add tests for Event Projection with follow up operations --- .../CustomProjection_follow_up_operations.cs | 8 +- ...ojectionWithCreate_follow_up_operations.cs | 111 ++++++++++++++++++ .../EventProjection_follow_up_operations.cs | 8 +- 3 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/EventProjectionWithCreate_follow_up_operations.cs diff --git a/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/CustomProjection_follow_up_operations.cs b/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/CustomProjection_follow_up_operations.cs index d1989ad01f..0da856d627 100644 --- a/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/CustomProjection_follow_up_operations.cs +++ b/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/CustomProjection_follow_up_operations.cs @@ -13,10 +13,6 @@ namespace Marten.AsyncDaemon.Testing.DocumentTrackingByIdentity; public class CustomProjection_follow_up_operations: DaemonContext { - public CustomProjection_follow_up_operations(ITestOutputHelper output): base(output) - { - } - [Fact] public async Task rebuild_with_follow_up_operations_should_work() { @@ -96,4 +92,8 @@ public async Task ApplyAsync(IDocumentOperations operations, IReadOnlyList x.Projections.Add(ProjectionLifecycle.Inline, + asyncOptions => asyncOptions.EnableDocumentTrackingByIdentity = true)); + + var entityId = Guid.NewGuid(); + + await using var session = theStore.IdentitySession(); + + session.Events.StartStream(entityId, new EntityCreated(entityId, "Some name")); + await session.SaveChangesAsync(); + session.Events.Append(entityId, new EntityNameUpdated(entityId, "New name")); + await session.SaveChangesAsync(); + + var agent = await StartDaemon(); + + await agent.RebuildProjection(nameof(EntityProjection), CancellationToken.None); + + var shoppingCartRebuilt = await session.LoadAsync(entityId); + + shoppingCartRebuilt!.Id.ShouldBe(entityId); + shoppingCartRebuilt.Name.ShouldBe("New name"); + } + + + [Fact] + public async Task regular_usage_follow_up_operations_should_work() + { + StoreOptions(x => x.Projections.Add(ProjectionLifecycle.Async, + asyncOptions => asyncOptions.EnableDocumentTrackingByIdentity = true)); + + var entityId = Guid.NewGuid(); + + await using var session = theStore.IdentitySession(); + + session.Events.StartStream(entityId, new EntityCreated(entityId, "Some name")); + await session.SaveChangesAsync(); + session.Events.Append(entityId, new EntityNameUpdated(entityId, "New name")); + await session.SaveChangesAsync(); + + var daemon = await StartDaemon(); + + await daemon.StartDaemon(); + + try + { + await daemon.Tracker.WaitForShardState($"{nameof(EntityProjection)}:All", 2, TimeSpan.FromSeconds(10)); + } + catch (Exception exc) + { + daemon.StatusFor($"{nameof(EntityProjection)}:All").ShouldBe(AgentStatus.Stopped); + } + + var entity = await session.LoadAsync(entityId); + + entity.ShouldNotBeNull(); + + entity.Id.ShouldBe(entityId); + entity.Name.ShouldBe("New name"); + } + + public record Entity(Guid Id, string Name); + + public record EntityCreated(Guid Id, string Name); + + public record EntityNameUpdated(Guid Id, string Name); + + public class EntityProjection: EventProjection + { + public EntityProjection() + { + ProjectionName = nameof(EntityProjection); + } + + public Entity Create(EntityCreated @event) + => new(@event.Id, @event.Name); + + public async Task Project(EntityNameUpdated @event, IDocumentOperations operations, + CancellationToken cancellationToken) + { + var stock = await operations.LoadAsync(@event.Id, cancellationToken).ConfigureAwait(false); + if (stock is null) + { + throw new ArgumentNullException(nameof(stock), "Stock does not exist!"); + } + + stock = stock with { Name = @event.Name }; + + operations.Store(stock); + } + } + + public EventProjectionWithCreate_follow_up_operations(ITestOutputHelper output): base(output) + { + } +} diff --git a/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/EventProjection_follow_up_operations.cs b/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/EventProjection_follow_up_operations.cs index 070e504c2e..aed00f612a 100644 --- a/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/EventProjection_follow_up_operations.cs +++ b/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/EventProjection_follow_up_operations.cs @@ -12,10 +12,6 @@ namespace Marten.AsyncDaemon.Testing.DocumentTrackingByIdentity; public class EventProjection_follow_up_operations: DaemonContext { - public EventProjection_follow_up_operations(ITestOutputHelper output): base(output) - { - } - [Fact] public async Task rebuild_with_follow_up_operations_should_work() { @@ -72,4 +68,8 @@ public NestedEntityEventProjection() }); } } + + public EventProjection_follow_up_operations(ITestOutputHelper output): base(output) + { + } } From 5cd4be907532bdb864f5f9d479d5d884ca229f9f Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 7 Jul 2023 18:01:43 +0200 Subject: [PATCH 2/3] Added proper handling for identity map when EnableDocumentTrackingByIdentity projections setting is toggled --- ...tProjectionWithCreate_follow_up_operations.cs | 9 +-------- .../Events/Daemon/ProjectionDocumentSession.cs | 16 +++++++++------- .../Internal/Sessions/DocumentSessionBase.cs | 16 ++++++++++++---- src/Marten/Internal/Sessions/QuerySession.cs | 6 ++++-- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/EventProjectionWithCreate_follow_up_operations.cs b/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/EventProjectionWithCreate_follow_up_operations.cs index 15e7bc85c6..d616b46021 100644 --- a/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/EventProjectionWithCreate_follow_up_operations.cs +++ b/src/Marten.AsyncDaemon.Testing/DocumentTrackingByIdentity/EventProjectionWithCreate_follow_up_operations.cs @@ -57,14 +57,7 @@ public async Task regular_usage_follow_up_operations_should_work() await daemon.StartDaemon(); - try - { - await daemon.Tracker.WaitForShardState($"{nameof(EntityProjection)}:All", 2, TimeSpan.FromSeconds(10)); - } - catch (Exception exc) - { - daemon.StatusFor($"{nameof(EntityProjection)}:All").ShouldBe(AgentStatus.Stopped); - } + await daemon.Tracker.WaitForShardState($"{nameof(EntityProjection)}:All", 2); var entity = await session.LoadAsync(entityId); diff --git a/src/Marten/Events/Daemon/ProjectionDocumentSession.cs b/src/Marten/Events/Daemon/ProjectionDocumentSession.cs index 441a5896d8..6336dfb5b0 100644 --- a/src/Marten/Events/Daemon/ProjectionDocumentSession.cs +++ b/src/Marten/Events/Daemon/ProjectionDocumentSession.cs @@ -11,16 +11,18 @@ namespace Marten.Events.Daemon; /// internal class ProjectionDocumentSession: DocumentSessionBase { - public ProjectionDocumentSession(DocumentStore store, ISessionWorkTracker workTracker, - SessionOptions sessionOptions): base( - store, sessionOptions, new MartenControlledConnectionTransaction(sessionOptions), workTracker) + public ProjectionDocumentSession( + DocumentStore store, + ISessionWorkTracker workTracker, + SessionOptions sessionOptions + ): base(store, sessionOptions, new MartenControlledConnectionTransaction(sessionOptions), workTracker) { } - protected internal override IDocumentStorage selectStorage(DocumentProvider provider) - { - return provider.Lightweight; - } + internal override DocumentTracking TrackingMode => SessionOptions.Tracking; + + protected internal override IDocumentStorage selectStorage(DocumentProvider provider) => + TrackingMode == DocumentTracking.IdentityOnly ? provider.IdentityMap : provider.Lightweight; protected internal override void ejectById(long id) { diff --git a/src/Marten/Internal/Sessions/DocumentSessionBase.cs b/src/Marten/Internal/Sessions/DocumentSessionBase.cs index 5296ce338c..e4eea5da51 100644 --- a/src/Marten/Internal/Sessions/DocumentSessionBase.cs +++ b/src/Marten/Internal/Sessions/DocumentSessionBase.cs @@ -20,15 +20,23 @@ public abstract partial class DocumentSessionBase: QuerySession, IDocumentSessio private Dictionary? _byTenant; - internal DocumentSessionBase(DocumentStore store, SessionOptions sessionOptions, IConnectionLifetime connection): - base(store, sessionOptions, connection) + internal DocumentSessionBase( + DocumentStore store, + SessionOptions sessionOptions, + IConnectionLifetime connection + ): base(store, sessionOptions, connection) { Concurrency = sessionOptions.ConcurrencyChecks; _workTracker = new UnitOfWork(this); } - internal DocumentSessionBase(DocumentStore store, SessionOptions sessionOptions, IConnectionLifetime connection, - ISessionWorkTracker workTracker, Tenant? tenant = default): base(store, sessionOptions, connection, tenant) + internal DocumentSessionBase( + DocumentStore store, + SessionOptions sessionOptions, + IConnectionLifetime connection, + ISessionWorkTracker workTracker, + Tenant? tenant = default + ): base(store, sessionOptions, connection, tenant) { Concurrency = sessionOptions.ConcurrencyChecks; _workTracker = workTracker; diff --git a/src/Marten/Internal/Sessions/QuerySession.cs b/src/Marten/Internal/Sessions/QuerySession.cs index ccb989727d..a936ee3fde 100644 --- a/src/Marten/Internal/Sessions/QuerySession.cs +++ b/src/Marten/Internal/Sessions/QuerySession.cs @@ -40,10 +40,12 @@ protected virtual IQueryEventStore CreateEventStore(DocumentStore store, Tenant public string TenantId { get; protected set; } #nullable enable - internal QuerySession(DocumentStore store, + internal QuerySession( + DocumentStore store, SessionOptions sessionOptions, IConnectionLifetime connection, - Tenant? tenant = default) + Tenant? tenant = default + ) { _store = store; TenantId = tenant?.TenantId ?? sessionOptions.Tenant?.TenantId ?? sessionOptions.TenantId; From 999d371d2c84b397aa1e62cc544bfc55ebbc39fa Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Fri, 7 Jul 2023 18:36:39 +0200 Subject: [PATCH 3/3] Bumped to 6.0.4 --- Directory.Build.props | 2 +- src/Marten.AspNetCore/Marten.AspNetCore.csproj | 2 +- src/Marten.NodaTime/Marten.NodaTime.csproj | 2 +- src/Marten.PLv8/Marten.PLv8.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f312905ef5..b9f9646be9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 6.0.3 + 6.0.4 11.0 Jeremy D. Miller;Babu Annamalai;Oskar Dudycz;Joona-Pekka Kokko https://martendb.io/logo.png diff --git a/src/Marten.AspNetCore/Marten.AspNetCore.csproj b/src/Marten.AspNetCore/Marten.AspNetCore.csproj index 85777bdfea..a1713d847d 100644 --- a/src/Marten.AspNetCore/Marten.AspNetCore.csproj +++ b/src/Marten.AspNetCore/Marten.AspNetCore.csproj @@ -3,7 +3,7 @@ net6.0;net7.0 Helpers for Marten-backed AspNetCore applications - 6.0.3 + 6.0.4 true true true diff --git a/src/Marten.NodaTime/Marten.NodaTime.csproj b/src/Marten.NodaTime/Marten.NodaTime.csproj index e6c6ad588d..bcb8042f39 100644 --- a/src/Marten.NodaTime/Marten.NodaTime.csproj +++ b/src/Marten.NodaTime/Marten.NodaTime.csproj @@ -1,7 +1,7 @@  NodaTime extension for Marten - 6.0.3 + 6.0.4 net6.0;net7.0 true true diff --git a/src/Marten.PLv8/Marten.PLv8.csproj b/src/Marten.PLv8/Marten.PLv8.csproj index 287f622803..96a9e53069 100644 --- a/src/Marten.PLv8/Marten.PLv8.csproj +++ b/src/Marten.PLv8/Marten.PLv8.csproj @@ -2,7 +2,7 @@ Document transforms and patching extension for Marten - 6.0.3 + 6.0.4 net6.0;net7.0 true true