MediatR 和 SimpleInjector 的依赖范围问题

Posted

技术标签:

【中文标题】MediatR 和 SimpleInjector 的依赖范围问题【英文标题】:Dependency Scope Issues with MediatR and SimpleInjector 【发布时间】:2016-12-01 14:38:31 【问题描述】:

我一直在使用实体框架进行数据访问的 WinForms 应用程序中使用 MediatR 库来试验中介者模式和 CQRS。该应用程序用于批量制造工厂,并允许用户查看活动和已完成批次的列表,并在必要时更新批次信息。每个批次都有大量与之相关的信息,例如质量和过程测量。根据这些文章,读取和写入数据被组织成查询和命令:

Meanwhile... on the query side of my architecture

CQRS with MediatR and AutoMapper

这是一个查询和查询处理程序的简单示例。 DataContext 使用 SimpleInjector 注入到查询处理程序中。

public class GetAllBatchesQuery: IRequest<IEnumerable<Batch>>  

public class GetAllBatchesQueryHandler :
    IRequestHandler<GetAllBatchesQuery, IEnumerable<Batch>>

    private readonly DataContext _context;

    public GetAllBatchesQueryHandler(DataContext context)
    
        _context= context;
    

    public IEnumerable<Batch> Handle(GetAllBatchesQueryrequest)
    
        return _db.Batches.ToList();
    

这将被演示者调用如下:

var batches = mediator.Send(new GetAllBatchesQuery());

我遇到的问题是 DbContext 的生命周期。理想情况下,我希望每个隔离事务使用一个实例,在这种情况下,它包括以下内容:

从数据库中检索批次列表 检索批次的质量指标列表(这些指标存储在不同的数据库中并通过存储过程访问) 更新批次,其中可能包括更新数据库中的多个实体

这将引导我走向 DbContext 的范围或短暂的生活方式。但是,当使用瞬态生活方式时,SimpleInjector 会引发以下错误,在注册类型时会抛出如下错误:

container.Register<DataContext>();

SimpleInjector.dll 中出现“SimpleInjector.DiagnosticVerificationException”类型的未处理异常

附加信息:配置无效。报告了以下诊断警告:

-[Disposable Transient Component] DataContext注册为transient,但实现了IDisposable。

在 SimpleInjector 网站上研究此问题后发现以下note:

警告:容器不跟踪瞬态实例。这意味着 Simple Injector 不会处理瞬态实例。

这使我走上了为 DataContext 使用 Lifetime Scope 生活方式的道路。为此,我为我的查询创建了一个新的装饰器类,并将其注册如下:

public class LifetimeScopeDecorator<TRequest, TResponse> :
    IRequestHandler<TRequest, TResponse>
    where TRequest : IRequest<TResponse>

    private readonly IRequestHandler<TRequest, TResponse> _decorated;
    private readonly Container _container;

    public LifetimeScopeDecorator(
        IRequestHandler<TRequest, TResponse> decorated,
        Container container)
    
        _decorated = decorated;
        _container = container;
    

    public TResponse Handle(TRequest message)
    
        using (_container.BeginLifetimeScope())
        
            var result = _decorated.Handle(message);
            return result;
        
    


...

container.RegisterDecorator(
    typeof(IRequestHandler<,>),
    typeof(ExecutionContextScopeDecorator<,>));

但是,进行该更改会导致不同的异常,这次在以下行抛出:

var batches = mediator.Send(new GetAllBatchesQuery());

MediatR.dll 中出现“System.InvalidOperationException”类型的未处理异常

附加信息:未找到 MediatorTest.GetAllBatchesQuery 类型请求的处理程序。

容器或服务定位器未正确配置或处理程序未在您的容器中注册。

在调试并查看 MediatR 代码后,似乎在调用 mediator.Send(...) 方法时,通过调用 container.GetInstance() 创建了 GetAllBatchesQueryHandler 类的新实例。但是由于此时DataContext不在执行范围内,可能没有正确初始化,导致异常。

我相信我了解问题的根本原因,但不知道如何有效地解决它。为了更好地说明这个问题,我开发了以下最小示例。任何实现IDisposable 的类都会导致与DataContext 相同的问题。

using System;
using System.Collections.Generic;
using System.Reflection;
using MediatR;
using SimpleInjector;
using SimpleInjector.Extensions.LifetimeScoping;

namespace MediatorTest

    public class GetRandomQuery : IRequest<int>
    
        public int Min  get; set; 
        public int Max  get; set; 
    

    public class GetRandomQueryHandler : IRequestHandler<GetRandomQuery, int>
    
        private readonly RandomNumberGenerator _r;

        public GetRandomQueryHandler(RandomNumberGenerator r)
        
            _r = r;
        

        public int Handle(GetRandomQuery request)
        
            return _r.Next(request.Min, request.Max);
        
    

    public class RandomNumberGenerator : IDisposable
    
        private Random _random = new Random();

        public RandomNumberGenerator()  

        public void Dispose()  

        public int Next(int min, int max)
        
            var result = _random.Next(min, max);
            return result;
        
    

    public class LifetimeScopeDecorator<TRequest, TResponse> :
        IRequestHandler<TRequest, TResponse>
        where TRequest : IRequest<TResponse>
    
        private readonly IRequestHandler<TRequest, TResponse> _decorated;
        private readonly Container _container;

        public LifetimeScopeDecorator(
            IRequestHandler<TRequest, TResponse> decorated,
            Container container)
        
            _decorated = decorated;
            _container = container;
        

        public TResponse Handle(TRequest message)
        
            using (_container.BeginLifetimeScope())
            
                var result = _decorated.Handle(message);
                return result;
            
        
    

    class Program
    
        static void Main(string[] args)
        
            var assemblies = GetAssemblies();

            var container = new Container();
            container.Options.DefaultScopedLifestyle = new LifetimeScopeLifestyle();
            container.RegisterSingleton<IMediator, Mediator>();
            container.Register<RandomNumberGenerator>(Lifestyle.Scoped);
            container.Register(typeof(IRequestHandler<,>), assemblies);
            container.RegisterSingleton(new SingleInstanceFactory(container.GetInstance));
            container.RegisterSingleton(new MultiInstanceFactory(container.GetAllInstances));
            container.RegisterDecorator(
                typeof(IRequestHandler<,>),
                typeof(LifetimeScopeDecorator<,>));

            container.Verify();

            var mediator = container.GetInstance<IMediator>();

            var value = mediator.Send(new GetRandomQuery()  Min = 1, Max = 100 );

            Console.WriteLine("Value = " + value);

            Console.ReadKey();
        

        private static IEnumerable<Assembly> GetAssemblies()
        
            yield return typeof(IMediator).GetTypeInfo().Assembly;
            yield return typeof(GetRandomQuery).GetTypeInfo().Assembly;
        
    

【问题讨论】:

【参考方案1】:

问题是您的装饰者(及其DbContext 依赖项)是在创建装饰器时创建的,并且当时没有活动范围(因为您在稍后的时间点创建它)。您应该使用here 描述的装饰工厂。换句话说,你的LifetimeScopeDecorator应该是这样实现的:

public class LifetimeScopeDecorator<TRequest, TResponse> :
    IRequestHandler<TRequest, TResponse>
    where TRequest : IRequest<TResponse>

    private readonly Func<IRequestHandler<TRequest, TResponse>> _decorateeFactory;
    private readonly Container _container;

    public LifetimeScopeDecorator(
        Func<IRequestHandler<TRequest, TResponse>> decorateeFactory,
        Container container)
    
        _decorateeFactory = decorateeFactory;
        _container = container;
    

    public TResponse Handle(TRequest message)
    
        using (_container.BeginLifetimeScope())
        
            var result = _decorateeFactory.Invoke().Handle(message);
            return result;
        
    

与原始实现的不同之处在于注入的是Func&lt;IRequestHandler&lt;TRequest, TResponse&gt;&gt;,而不是IRequestHandler&lt;TRequest, TResponse&gt;。这允许 Simple Injector 在创建范围后推迟创建。

【讨论】:

完美运行!谢谢!

以上是关于MediatR 和 SimpleInjector 的依赖范围问题的主要内容,如果未能解决你的问题,请参考以下文章

csharp SimpleInjectorの基本的な使い方メモ

SimpleInjector不起作用--OWIN上的Web API

具有通用处理程序和查询的 Mediatr

Mediatr - 使缓存无效/更新的正确位置在哪里

MediatR 和 CQRS 测试。如何验证调用了该处理程序?

我们可以通过 masstransit 一起使用 RabbitMQ 和 Mediatr 吗?