MediatR 和 SimpleInjector 的依赖范围问题

2024-04-21

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

同时......在我的架构的查询方面 https://cuttingedge.it/blogs/steven/pivot/entry.php?id=92

带有 MediatR 和 AutoMapper 的 CQRS https://lostechies.com/jimmybogard/2015/05/05/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”类型的未处理异常

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

-[一次性瞬态组件] DataContext 注册为瞬态,但实现 IDisposable。

在 SimpleInjector 网站上研究这个问题揭示了以下内容note https://simpleinjector.readthedocs.io/en/latest/lifetimes.html#transient:

警告:容器不跟踪瞬态实例。这意味着简单注入器不会处理瞬态实例。

这引导我走上了为 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(...)方法被调用,一个新的实例GetAllBatchesQueryHandler类是通过调用创建的container.GetInstance()。然而,自从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;
        }
    }
}

问题是你的装饰者(及其DbContext依赖项)是在创建装饰器时创建的,并且当时没有活动作用域(因为您在稍后的时间点创建了它)。您应该按照描述使用装饰工厂here https://simpleinjector.readthedocs.io/en/latest/advanced.html#decorators-with-func-t-decoratee-factories。换句话说,你的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<IRequestHandler<TRequest, TResponse>>被注入而不是IRequestHandler<TRequest, TResponse>。这允许简单注入器在创建范围后推迟创建。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MediatR 和 SimpleInjector 的依赖范围问题 的相关文章

随机推荐

  • 使用当前 HTTP 请求身份作为 SharpSVN 的默认凭据

    我正在尝试通过 Web 应用程序调用 SharpSVN 中的 RemoteCreateDirectories 并希望凭据是已登录用户的凭据 这可以隐式完成还是需要用户名和密码 如下例所示 using var svnClient new Sv
  • MySQL 中可以将一个别名除以另一个别名吗?

    我有一个多表查询 与此类似 简化版 SELECT columns count table2 rev id As rev count sum table2 rev rating As sum rev rating FROM table1 LE
  • D3.js 如何将我的真实数据合并到饼图中

    我是 D3 和数据可视化的新手 在加载真实数据时遇到一些问题 您将在以下部分中找到我的代码 现在我有一些数据存储在数组中 现在我想做的是将数据库中的实际数据存储到饼图中 另外 如果我这样做 var mydata d3 json mydata
  • 如何在Anaconda Python(Windows平台)中安装xgboost?

    我是一个 Python 新用户 我从以下链接下载了最新的 Anaconda 3 2 4 1 Python 3 5 https www continuum io downloads https www continuum io downloa
  • MySQL选择结果保存到C#变量中

    你能检查我的代码并回答我如何将 mysql 选择结果保存到 C 字符串中吗 try MySqlDataReader reader null string selectCmd SELECT FROM TabelaUtilizatori MyS
  • ReactiveCommand 传递命令参数

    我想用命令来实现文本框中的KeyDown事件 我想让命令能够识别哪个键输入 例如 KeyEventArgs 在 KeyDown Event 中执行操作并执行其他一些操作 所以我想将命令参数传递给ReactiveCommand 就像Event
  • Python selenium:DevTools 监听 ws://127.0.0.1

    今天 当我使用 chromedriver 运行 selenium 时 我在控制台上收到此消息 我该如何抑制这种情况 DevTools listening on ws 127 0 0 1 12740 devtools browser 9710
  • 更改 Integration Services 项目中的 .NET Framework

    在 Visual Studio 2013 中创建新的 Integration Services 项目时 我可以选择要定位的 NET 框架 如何查看现有项目所针对的 NET 框架并可能对其进行更改 您需要打开脚本任务之一并单击 编辑脚本 按钮
  • 如何在 R 中按下传单弹出窗口时创建事件?

    当我单击传单多边形时 我想让 tabPanel 变为闪亮 我对如何做到这一点有一些想法 但我找不到实现它们所需的信息 我在选项卡面板中有传单 但我想在单击多边形时切换到另一个选项卡 leaflet llmap gt addTiles gt
  • 使用 cmake 构建项目后如何运行 ctest

    我希望每次成功构建项目时都启动测试 如果某些测试被破坏 我希望我的构建也被破坏 默认情况下 我需要通过运行来手动运行测试ctest命令 CTest 实际上可以构建项目 但我使用调用的 IDEmake建立资源 和make不运行测试 我将此命令
  • php 7 无法初始化 sqlsrv

    我搜索了一整天 寻找 php 7 VC14 x64 Thread Safe 上 sqlsrv dll 的解决方案 但没有找到解决方案 有没有人解决这个问题 04 Oct 2015 19 48 05 UTC PHP Warning PHP S
  • UNNotificationServiceExtension:内存限制?

    我正在尝试实现 UNNotificationServiceExtension 但我的代码似乎经常失败 只需说明 Program ended with exit code 0 我正在尝试在扩展中使用 FMDB Sqlite3 模块 似乎我可能
  • 诊断 SQL Server 2005 中的死锁

    我们在 Stack Overflow SQL Server 2005 数据库中发现了一些有害但罕见的死锁情况 我附加了分析器 使用设置了跟踪配置文件这篇关于解决死锁问题的优秀文章 http www simple talk com sql l
  • 如何仅将缩进序列化应用于某些属性?

    我想以人类可读的方式将 NET 对象序列化为 JSON 但我希望对对象的属性或数组的元素是否最终位于自己的一行上有更多的控制 目前我正在使用 JSON NETJsonConvert SerializeObject object Format
  • 如何改变字典中的数组?

    我在操场上尝试过以下操作 var d1 String String d1 a String var a1 d1 a a1 append s1 println d1 输出是 a 我希望 a s1 改变字典中数组的正确方法是什么 在 swift
  • C++:不同翻译单元中具有相同名称的不同类

    考虑以下示例 usedclass1 hpp include
  • pmap 和线程数

    user gt Runtime getRuntime availableProcessors 2 并评估这个例子 http clojuredocs org clojure core clojure core pmap example 684
  • “PKIX 路径构建失败”和“无法找到请求目标的有效证书路径”

    我正在尝试使用 twitter4j 库为我的 java 项目获取推文 该项目在幕后使用java net HttpURLConnection 如堆栈跟踪中所示 第一次运行时 我收到有关证书的错误sun security validator V
  • PyQt:QFileSystemModel 复选框过滤器

    我正在尝试使用 python pyqt 创建一个实用程序来从QFileSystemModel 仅包括已检查的项目 现在我想要控制QFileSystemModel使用文件名 文件类型 文件大小进行过滤的复选框 我如何勾选 取消勾选QFileS
  • MediatR 和 SimpleInjector 的依赖范围问题

    我一直在使用中介者模式和 CQRS 进行实验MediatR https github com jbogard MediatR使用实体框架进行数据访问的 WinForms 应用程序中的库 该应用程序用于批次制造工厂 允许用户查看活动批次和已完