From 4c7717f165d3358fda2e52d3318ca12425547946 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Mon, 4 Nov 2024 10:51:26 -0600 Subject: [PATCH] Addressed code generation issue with transactional middleware application against services in implied middleware. Closes GH-1118 --- src/Http/Wolverine.Http/HttpChain.cs | 2 +- .../EfCoreTests/EfCoreCompilationScenarios.cs | 2 + .../EfCoreTests/SampleDbContext.cs | 1 + .../end_to_end_efcore_persistence.cs | 56 ++++++++++++++++++- .../Attributes/TransactionalAttribute.cs | 1 + src/Wolverine/Configuration/Chain.cs | 9 ++- src/Wolverine/Configuration/IChain.cs | 3 + .../Persistence/AutoApplyTransactions.cs | 1 + .../Runtime/Handlers/HandlerChain.cs | 4 +- 9 files changed, 75 insertions(+), 4 deletions(-) diff --git a/src/Http/Wolverine.Http/HttpChain.cs b/src/Http/Wolverine.Http/HttpChain.cs index 07911f2c5..172e3453b 100644 --- a/src/Http/Wolverine.Http/HttpChain.cs +++ b/src/Http/Wolverine.Http/HttpChain.cs @@ -114,7 +114,7 @@ public HttpChain(MethodCall method, HttpGraph parent) applyAttributesAndConfigureMethods(_parent.Rules, _parent.Container); // Add Before/After methods from the current handler - applyImpliedMiddlewareFromHandlers(_parent.Rules); + ApplyImpliedMiddlewareFromHandlers(_parent.Rules); foreach (var call in Middleware.OfType().ToArray()) { diff --git a/src/Persistence/EfCoreTests/EfCoreCompilationScenarios.cs b/src/Persistence/EfCoreTests/EfCoreCompilationScenarios.cs index 4c5105c89..9fbeea631 100644 --- a/src/Persistence/EfCoreTests/EfCoreCompilationScenarios.cs +++ b/src/Persistence/EfCoreTests/EfCoreCompilationScenarios.cs @@ -66,4 +66,6 @@ public class Item { public Guid Id { get; set; } public string Name { get; set; } + + public bool Approved { get; set; } } \ No newline at end of file diff --git a/src/Persistence/EfCoreTests/SampleDbContext.cs b/src/Persistence/EfCoreTests/SampleDbContext.cs index 746006e3e..6e5011761 100644 --- a/src/Persistence/EfCoreTests/SampleDbContext.cs +++ b/src/Persistence/EfCoreTests/SampleDbContext.cs @@ -21,6 +21,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) map.ToTable("items"); map.HasKey(x => x.Id); map.Property(x => x.Name); + map.Property(x => x.Approved); }); } } diff --git a/src/Persistence/EfCoreTests/end_to_end_efcore_persistence.cs b/src/Persistence/EfCoreTests/end_to_end_efcore_persistence.cs index ef393bd06..a5304df31 100644 --- a/src/Persistence/EfCoreTests/end_to_end_efcore_persistence.cs +++ b/src/Persistence/EfCoreTests/end_to_end_efcore_persistence.cs @@ -11,11 +11,13 @@ using Weasel.SqlServer; using Weasel.SqlServer.Tables; using Wolverine; +using Wolverine.Attributes; using Wolverine.EntityFrameworkCore; using Wolverine.EntityFrameworkCore.Internals; using Wolverine.Persistence.Durability; using Wolverine.Runtime; using Wolverine.SqlServer; +using Wolverine.Tracking; using Wolverine.Transports; namespace EfCoreTests; @@ -35,7 +37,7 @@ public EFCorePersistenceContext() : base(true) options.PersistMessagesWithSqlServer(Servers.SqlServerConnectionString); options.Services.AddResourceSetupOnStartup(StartupAction.ResetState); options.UseEntityFrameworkCoreTransactions(); - + options.Policies.ConfigureConventionalLocalRouting() .CustomizeQueues((_, q) => q.UseDurableInbox()); }); @@ -43,6 +45,7 @@ public EFCorePersistenceContext() : base(true) ItemsTable = new Table("items"); ItemsTable.AddColumn("Id").AsPrimaryKey(); ItemsTable.AddColumn("Name"); + ItemsTable.AddColumn("Approved"); } public Table ItemsTable { get; } @@ -60,6 +63,40 @@ public end_to_end_efcore_persistence(EFCorePersistenceContext context) public Table ItemsTable { get; } public IHost Host { get; } + + + [Fact] + public async Task using_dbcontext_in_middleware() + { + await withItemsTable(); + + var item = new Item { Id = Guid.NewGuid(), Name = "Hey"}; + await saveItem(item); + + await Host.InvokeMessageAndWaitAsync(new ApproveItem(item.Id)); + + var existing = await loadItem(item.Id); + existing.Approved.ShouldBeTrue(); + } + + private async Task loadItem(Guid id) + { + using var nested = Host.Services.CreateScope(); + + var context = nested.ServiceProvider.GetRequiredService(); + var item = await context.Items.FindAsync(id); + + return item; + } + + private async Task saveItem(Item item) + { + using var nested = Host.Services.CreateScope(); + + var context = nested.ServiceProvider.GetRequiredService(); + context.Items.Add(item); + await context.SaveChangesAsync(); + } [Fact] public void service_registrations() @@ -459,6 +496,7 @@ public async Task persist_an_incoming_envelope_mapped() loadedEnvelope.OwnerId.ShouldBe(envelope.OwnerId); loadedEnvelope.Attempts.ShouldBe(envelope.Attempts); } + } public class PassRecorder @@ -492,4 +530,20 @@ public class Pass { public string From { get; set; } public string To { get; set; } +} + +public record ApproveItem(Guid Id); + +public static class ApproveItemHandler +{ + public static ValueTask LoadAsync(ApproveItem command, SampleDbContext dbContext) + { + return dbContext.Items.FindAsync(command.Id); + } + + [Transactional] + public static void Handle(ApproveItem command, Item item) + { + item.Approved = true; + } } \ No newline at end of file diff --git a/src/Wolverine/Attributes/TransactionalAttribute.cs b/src/Wolverine/Attributes/TransactionalAttribute.cs index 40976fa8f..72d1ba06c 100644 --- a/src/Wolverine/Attributes/TransactionalAttribute.cs +++ b/src/Wolverine/Attributes/TransactionalAttribute.cs @@ -14,6 +14,7 @@ public class TransactionalAttribute : ModifyChainAttribute { public override void Modify(IChain chain, GenerationRules rules, IServiceContainer container) { + chain.ApplyImpliedMiddlewareFromHandlers(rules); var transactionFrameProvider = rules.As().GetPersistenceProviders(chain, container); transactionFrameProvider.ApplyTransactionSupport(chain, container); } diff --git a/src/Wolverine/Configuration/Chain.cs b/src/Wolverine/Configuration/Chain.cs index 8cd8c90bf..c8606ab80 100644 --- a/src/Wolverine/Configuration/Chain.cs +++ b/src/Wolverine/Configuration/Chain.cs @@ -125,7 +125,9 @@ protected void applyAttributesAndConfigureMethods(GenerationRules rules, IServic var genericMethodAtts = handlers.SelectMany(x => x.Method.GetCustomAttributes()); foreach (var attribute in genericHandlerAtts.Concat(genericMethodAtts)) + { attribute.Modify(this, rules, container); + } } private static Type[] _typesToIgnore = new Type[] @@ -223,8 +225,13 @@ private IEnumerable serviceDependencies(IServiceContainer container, IRead } } - protected void applyImpliedMiddlewareFromHandlers(GenerationRules generationRules) + private bool _appliedImpliedMiddleware; + + public void ApplyImpliedMiddlewareFromHandlers(GenerationRules generationRules) { + if (_appliedImpliedMiddleware) return; + _appliedImpliedMiddleware = true; + var handlerTypes = HandlerCalls().Select(x => x.HandlerType).Distinct(); foreach (var handlerType in handlerTypes) { diff --git a/src/Wolverine/Configuration/IChain.cs b/src/Wolverine/Configuration/IChain.cs index 36baf2139..80c021089 100644 --- a/src/Wolverine/Configuration/IChain.cs +++ b/src/Wolverine/Configuration/IChain.cs @@ -1,4 +1,5 @@ using System.Reflection; +using JasperFx.CodeGeneration; using JasperFx.CodeGeneration.Frames; using JasperFx.CodeGeneration.Model; using JasperFx.Core.Reflection; @@ -98,6 +99,8 @@ public IEnumerable ReturnVariablesOfType() /// /// public void AddDependencyType(Type type); + + void ApplyImpliedMiddlewareFromHandlers(GenerationRules generationRules); } #endregion \ No newline at end of file diff --git a/src/Wolverine/Persistence/AutoApplyTransactions.cs b/src/Wolverine/Persistence/AutoApplyTransactions.cs index bd7c312aa..a11e745dd 100644 --- a/src/Wolverine/Persistence/AutoApplyTransactions.cs +++ b/src/Wolverine/Persistence/AutoApplyTransactions.cs @@ -18,6 +18,7 @@ public void Apply(IReadOnlyList chains, GenerationRules rules, IServiceC foreach (var chain in chains.Where(x => !x.HasAttribute())) { + chain.ApplyImpliedMiddlewareFromHandlers(rules); var potentials = providers.Where(x => x.CanApply(chain, container)).ToArray(); if (potentials.Length == 1) { diff --git a/src/Wolverine/Runtime/Handlers/HandlerChain.cs b/src/Wolverine/Runtime/Handlers/HandlerChain.cs index b32353a31..d9440aacb 100644 --- a/src/Wolverine/Runtime/Handlers/HandlerChain.cs +++ b/src/Wolverine/Runtime/Handlers/HandlerChain.cs @@ -416,6 +416,8 @@ protected void applyCustomizations(GenerationRules rules, IServiceContainer cont if (!_hasConfiguredFrames) { _hasConfiguredFrames = true; + + applyAttributesAndConfigureMethods(rules, container); @@ -427,7 +429,7 @@ protected void applyCustomizations(GenerationRules rules, IServiceContainer cont .OfType()) attribute.Modify(this, rules, container); } - applyImpliedMiddlewareFromHandlers(rules); + ApplyImpliedMiddlewareFromHandlers(rules); } protected IEnumerable determineHandlerReturnValueFrames()