2016-07-27 81 views
1

我一直在使用Entity Framework进行数据访问的WinForms应用程序中使用MediatR库的中介模式和CQRS进行试验。该应用程序用于批量生产工厂,并允许用户查看活动批次和完成批次的列表,并在必要时更新批次信息。每个批次都有大量与之相关的信息,例如质量和过程测量。读取和写入数据被组织到查询和命令的基础上,这些文章:MediatR和SimpleInjector的依赖范围问题

Meanwhile... on the query side of my architecture

CQRS with MediatR and AutoMapper

下面是一个查询和查询处理的一个简单的例子。使用SimpleInjector将DataContext注入查询处理程序。

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.DiagnosticVerificationException”的未处理的异常发生在SimpleInjector.dll

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

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

研究的SimpleInjector网站在这个问题上显示了以下note

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

这使我失望使用DataContext的一生范围生活方式的路径。要做到这一点,我创建了一个新的装饰类为我的查询,如下注册它:

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

“System.InvalidOperationException”类型的未处理的异常发生在MediatR.dll

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

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

经过调试,并通过MediatR代码看,似乎当mediator.Send(...)方法被调用时,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; 
     } 
    } 
} 

回答

2

的问题是,你的decoratee(其DbContext依赖)是在装饰创建时创建的,当时没有活动范围(因为你在稍后的时间点创建) 。您应该使用装饰工厂here

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>的:换句话说,如下您LifetimeScopeDecorator应予执行。这允许简单注射器在范围创建后推迟创建。

+1

完美的作品!谢谢! –