本文介绍了MediatR和SimpleInjector的依赖范围问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've been experimenting with the mediator pattern and CQRS using the MediatR library in a WinForms application that uses the Entity Framework for data access. The application is used in a batch manufacturing plant, and allows users to see a list of active and completed batches, and if necessary make updates to batch information. Each batch has a large amount of information associated with it, such as quality and process measurements. Reading and writing data is organized into Queries and Commands, based on these articles:

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

Here is a simple example of a query and query handler. DataContext is injected into the query handler using 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();
    }
}

这将从演示者调用如下: / p>

This would be called from the presenter as follows:

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

我遇到的问题是DbContext的生命周期。理想情况下,我想为每个独立的交易使用单个实例,在这种情况下,它将包括以下内容:

The problem that I'm running into is with the lifetime of the DbContext. Ideally, I'd like to use a single instance per isolated transaction, which in this case would include such things as:


  • 检索列表数据库中的批次

  • 检索批次的质量指标列表(这些列表存储在不同的数据库中,并通过存储过程进行访问)

  • 更新批次,其中可能包括更新数据库中的多个实体

这将导致我对DbContext的范围或暂时的生活方式。然而,当使用暂时的生活方式时,SimpleInjector会引发以下错误:在注册类型时抛出以下错误:

This would lead me towards a scoped or transient lifestyle for DbContext. However, when using a transient lifestyle, SimpleInjector raises the following error, which is thrown when registering the type as follows:

container.Register<DataContext>();



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

Additional information: The configuration is invalid. The following diagnostic warnings were reported:

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

-[Disposable Transient Component] DataContext is registered as transient, but implements IDisposable.

在SimpleInjector网站上研究此问题会显示以下:

Researching this issue on the SimpleInjector website reveals the following note:

这导致了我对DataContext使用Lifetime Scope生活方式的路径。为了实现这一点,我为我的查询创建了一个新的装饰器类,并注册如下:

This led me down the path of using a Lifetime Scope lifestyle for the DataContext. To achieve this, I created a new decorator class for my queries and registered it as follows:

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<,>));

但是,使此更改导致不同的异常,此时抛出以下行:

However, making that change causes a different exception, this time thrown at the following line:

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



其他信息:没有找到Handler,请求类型MediatorTest.GetAllBatchesQuery。

Additional information: Handler was not found for request of type MediatorTest.GetAllBatchesQuery.

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

Container or service locator not configured properly or handlers not registered with your container.

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

After debugging and looking through the MediatR code, it appears that when the mediator.Send(...) method is called, a new instance of the GetAllBatchesQueryHandler class is created by calling container.GetInstance(). However, since DataContext is not within an execution scope at this point, it may not be properly initialized, causing the exception.

我相信我了解这个问题的根本原因,但是如何有效地解决这个问题,我感到失落。为了更好地说明这个问题,我开发了以下最小的例子。任何实现 IDisposable 的类将导致与 DataContext 相同的问题。

I believe I understand the root cause of the issue, but am at a loss as to how to effectively resolve it. To help illustrate this problem better, I developed the following minimal example. Any class that implements IDisposable will result in the same issue that I am having with 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;
        }
    }
}


推荐答案

问题在于,您的装饰(与其 DbContext 依赖关系)在创建装饰器时创建,并且当时没有活动的作用域(因为你可以在稍后的时间点创建它)。您应该使用一个装饰工厂,如。换句话说,您的 LifetimeScopeDecorator 应该执行如下:

The problem is that your decoratee (with its DbContext dependency) is created at the time the decorator is created, and at that time there is no active scope (since you create it at a later point in time). You should use a decoratee factory as decribed here. In other words, your LifetimeScopeDecorator should be implemented as follows:

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< IRequestHandler< TRequest,TResponse> 而不是 IRequestHandler< TRequest,TResponse> 。这允许简单注射器在创建作用域之后推迟创建。

The difference with your original implementation is that a Func<IRequestHandler<TRequest, TResponse>> is injected instead of an IRequestHandler<TRequest, TResponse>. This allows Simple Injector to postpone the creation after the scope has been created.

这篇关于MediatR和SimpleInjector的依赖范围问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-24 00:53