我一直在使用中介者模式和 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;
}
}
}