diff --git a/changelog.md b/changelog.md index c380e03..1c88dd1 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## [6.0.0] - 2026-05-28 +### *Changes (Breaking)* +- RabbitMessageRouter now takes `IServiceProvider` instead of `IEnumerable` and creates a DI scope per received message. This ensures scoped services (e.g. DbContext) are fresh for each message, preventing stale tracked entities from leaking across messages. +- Consumers must register handlers as Scoped (not Singleton) and pass `IServiceProvider` to the router. +- Added `Microsoft.Extensions.DependencyInjection.Abstractions` dependency. + ## [5.2.0] - 2026-05-19 ### *Changes* - Make JsonMessageHandler.HandleAsync(T, headers, ct) public instead of protected, allowing consuming applications to unit test their handler implementations directly with typed payloads rather than having to construct serialised Message objects diff --git a/src/Linn.Common.Messaging.RabbitMQ.csproj b/src/Linn.Common.Messaging.RabbitMQ.csproj index 6b37234..5f13cc7 100644 --- a/src/Linn.Common.Messaging.RabbitMQ.csproj +++ b/src/Linn.Common.Messaging.RabbitMQ.csproj @@ -5,11 +5,12 @@ enable Linn.Common.Messaging.RabbitMQ Linn.Common.Messaging.RabbitMQ - 5.2.0 + 6.0.0 enable + diff --git a/src/RabbitMessageRouter.cs b/src/RabbitMessageRouter.cs index df37634..79233bd 100644 --- a/src/RabbitMessageRouter.cs +++ b/src/RabbitMessageRouter.cs @@ -1,4 +1,5 @@ -using RabbitMQ.Client; +using Microsoft.Extensions.DependencyInjection; +using RabbitMQ.Client; using RabbitMQ.Client.Events; namespace Linn.Common.Messaging.RabbitMQ; @@ -6,19 +7,14 @@ namespace Linn.Common.Messaging.RabbitMQ; public class RabbitMessageRouter { private readonly IChannel channel; - private readonly Dictionary handlers; + private readonly IServiceProvider serviceProvider; public RabbitMessageRouter( IChannel channel, - IEnumerable handlers) + IServiceProvider serviceProvider) { this.channel = channel; - - this.handlers = handlers - .GroupBy(h => h.RoutingKey) - .ToDictionary( - g => g.Key, - g => g.Single()); + this.serviceProvider = serviceProvider; } public AsyncEventingBasicConsumer CreateConsumer( @@ -28,7 +24,11 @@ public AsyncEventingBasicConsumer CreateConsumer( consumer.ReceivedAsync += async (_, ea) => { - if (!this.handlers.TryGetValue(ea.RoutingKey, out var handler)) + await using var scope = this.serviceProvider.CreateAsyncScope(); + var handler = scope.ServiceProvider.GetServices() + .FirstOrDefault(h => h.RoutingKey == ea.RoutingKey); + + if (handler == null) { Console.WriteLine( $"No handler registered for routing key '{ea.RoutingKey}'");