2016-07-27 1 views
1

데이터 액세스를 위해 Entity Framework를 사용하는 WinForms 응용 프로그램에서 MediatR 라이브러리를 사용하여 조정자 패턴과 CQRS를 실험했습니다. 이 응용 프로그램은 배치 제조 공장에서 사용되며 사용자는 활성 및 완료된 배치 목록을 볼 수 있으며 필요한 경우 배치 정보를 업데이트 할 수 있습니다. 각 배치에는 품질 및 프로세스 측정과 같은 많은 양의 정보가 연관되어 있습니다. 데이터 읽기 및 쓰기가이 기사를 기반으로, 쿼리 및 명령으로 구성되어 있습니다 : 여기 MediatR 및 SimpleInjector의 종속성 범위 문제

Meanwhile... on the query side of my architecture

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에 대한 스코프 또는 일시적인 생활 양식으로 나를 이끌 것입니다 (이 저장 프로 시저를 통해 다른 데이터베이스에 저장하고 액세스 할 수 있습니다) .

container.Register<DataContext>(); 

형 'SimpleInjector.DiagnosticVerificationException'처리되지 않은 예외 SimpleInjector.dll

에서 발생 과도 라이프를 사용하는 경우에는, SimpleInjector는 종류를 등록 할 때 다음과 같이 발생되는 다음 오류 제기

추가 정보 : 구성이 유효하지 않습니다. 다음과 같은 진단 경고가보고되었습니다.

- [Disposable Transient Component] DataContext는 일시적으로 등록되지만 IDisposable을 구현합니다.

경고 : 다음 SimpleInjector 웹 사이트에서이 문제를 검토 한 결과

note 다음을 보여 과도 인스턴스가 컨테이너에 의해 추적되지 않습니다. 즉, Simple Injector는 일시적인 인스턴스를 처리하지 않습니다.

이렇게하면 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()); 

'System.InvalidOperationException'형식의 처리되지 않은 예외 MediatR.dll

에 추가 정보를 발생 핸들러 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; 
     } 
    } 
} 

답변

2

문제는 (그 DbContext 의존성) 당신의 decoratee은 장식이 작성 될 때 만들어 졌는지, 그리고 그 시간에 활성 범위는 (당신이 시간에 나중에 그것을 만들 수 있기 때문에)이 없다 . 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하여 구현되어야한다. 이를 통해 Simple Injector는 스코프가 생성 된 후 생성을 연기 할 수 있습니다.

+1

완벽하게 작동합니다! 고맙습니다! –