diff --git a/src/Persistence/EfCoreTests/MultiTenancy/MultiTenancyCompliance.cs b/src/Persistence/EfCoreTests/MultiTenancy/MultiTenancyCompliance.cs
index e39cb9932..52698a60a 100644
--- a/src/Persistence/EfCoreTests/MultiTenancy/MultiTenancyCompliance.cs
+++ b/src/Persistence/EfCoreTests/MultiTenancy/MultiTenancyCompliance.cs
@@ -179,6 +179,39 @@ public async Task end_to_end_with_default_database()
}
}
+ [Fact]
+ public async Task end_to_end_with_cascading_messages()
+ {
+ var blueId = Guid.NewGuid();
+ var redId = Guid.NewGuid();
+ var greenId = Guid.NewGuid();
+
+ await theHost.InvokeMessageAndWaitAsync(new StartAndTriggerApproval(blueId, "Blue!"), "blue");
+ await theHost.InvokeMessageAndWaitAsync(new StartAndTriggerApproval(redId, "Red!"), "red");
+ await theHost.InvokeMessageAndWaitAsync(new StartAndTriggerApproval(greenId, "Green!"), "green");
+
+ var blueDbContext = await theBuilder.BuildAsync("blue", CancellationToken.None);
+ var greenDbContext = await theBuilder.BuildAsync("green", CancellationToken.None);
+ var redDbContext = await theBuilder.BuildAsync("red", CancellationToken.None);
+
+ var blue = await blueDbContext.Items.FindAsync(blueId);
+ blue.Name.ShouldBe("Blue!");
+ blue.Approved.ShouldBeTrue();
+ (await greenDbContext.Items.FindAsync(blueId)).ShouldBeNull();
+ (await redDbContext.Items.FindAsync(blueId)).ShouldBeNull();
+
+ (await blueDbContext.Items.FindAsync(redId)).ShouldBeNull();
+ (await greenDbContext.Items.FindAsync(redId)).ShouldBeNull();
+ var red = await redDbContext.Items.FindAsync(redId);
+ red.Name.ShouldBe("Red!");
+ red.Approved.ShouldBeTrue();
+
+ (await blueDbContext.Items.FindAsync(greenId)).ShouldBeNull();
+ var green = await greenDbContext.Items.FindAsync(greenId);
+ green.Name.ShouldBe("Green!");
+ green.Approved.ShouldBeTrue();
+ (await redDbContext.Items.FindAsync(greenId)).ShouldBeNull();
+ }
[Fact]
public async Task with_http_posts_using_storage_actions()
diff --git a/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EFCorePersistenceFrameProvider.cs b/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EFCorePersistenceFrameProvider.cs
index ea4273080..557d884fa 100644
--- a/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EFCorePersistenceFrameProvider.cs
+++ b/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EFCorePersistenceFrameProvider.cs
@@ -131,7 +131,9 @@ public void ApplyTransactionSupport(IChain chain, IServiceContainer container)
if (isMultiTenanted(container, dbContextType))
{
var createContext = typeof(CreateTenantedDbContext<>).CloseAndBuildAs(dbContextType);
+
chain.Middleware.Insert(0, createContext);
+ chain.Middleware.Insert(0, new EnrollTenantedDbContextInTransaction(dbContextType, chain.Idempotency));
}
else
{
@@ -171,6 +173,7 @@ public void ApplyTransactionSupport(IChain chain, IServiceContainer container, T
{
var createContext = typeof(CreateTenantedDbContext<>).CloseAndBuildAs(dbType);
chain.Middleware.Insert(0, createContext);
+ chain.Middleware.Insert(0, new EnrollTenantedDbContextInTransaction(dbType, chain.Idempotency));
}
else
{
diff --git a/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EnrollTenantedDbContextInTransaction.cs b/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EnrollTenantedDbContextInTransaction.cs
new file mode 100644
index 000000000..8fcd9f09d
--- /dev/null
+++ b/src/Persistence/Wolverine.EntityFrameworkCore/Codegen/EnrollTenantedDbContextInTransaction.cs
@@ -0,0 +1,62 @@
+using JasperFx.CodeGeneration;
+using JasperFx.CodeGeneration.Frames;
+using JasperFx.CodeGeneration.Model;
+using JasperFx.Core.Reflection;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Storage;
+using Wolverine.Persistence;
+using Wolverine.Runtime;
+
+namespace Wolverine.EntityFrameworkCore.Codegen;
+
+internal class EnrollTenantedDbContextInTransaction : AsyncFrame
+{
+ private readonly Type _dbContextType;
+ private readonly IdempotencyStyle _idempotencyStyle;
+
+ private Variable _dbContext;
+ private Variable _cancellation;
+ private Variable? _context;
+
+ public EnrollTenantedDbContextInTransaction(Type dbContextType, IdempotencyStyle idempotencyStyle)
+ {
+ _dbContextType = dbContextType;
+ _idempotencyStyle = idempotencyStyle;
+ }
+
+ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
+ {
+ writer.Write("BLOCK:try");
+
+ // EF Core can only do eager idempotent checks
+ if (_idempotencyStyle == IdempotencyStyle.Eager || _idempotencyStyle == IdempotencyStyle.Optimistic)
+ {
+ writer.Write($"await {_context.Usage}.{nameof(MessageContext.AssertEagerIdempotencyAsync)}({_cancellation.Usage});");
+ }
+
+ writer.Write($"BLOCK:if ({_dbContext.Usage}.Database.CurrentTransaction == null)");
+ writer.Write($"await {_dbContext.Usage}.Database.BeginTransactionAsync({_cancellation.Usage});");
+ writer.FinishBlock();
+
+ Next?.GenerateCode(method, writer);
+
+ writer.Write($"await {_dbContext.Usage}.Database.CommitTransactionAsync({_cancellation.Usage});");
+ writer.FinishBlock();
+ writer.Write($"BLOCK:catch ({typeof(Exception).FullNameInCode()})");
+ writer.Write($"await {_dbContext.Usage}.Database.RollbackTransactionAsync({_cancellation.Usage});");
+ writer.Write("throw;");
+ writer.FinishBlock();
+ }
+
+ public override IEnumerable FindVariables(IMethodVariables chain)
+ {
+ _context = chain.FindVariable(typeof(MessageContext));
+ yield return _context;
+
+ _dbContext = chain.FindVariable(_dbContextType);
+ yield return _dbContext;
+
+ _cancellation = chain.FindVariable(typeof(CancellationToken));
+ yield return _cancellation;
+ }
+}