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

完美的作品!謝謝! –