4

データ アクセスに Entity Framework を使用する WinForms アプリケーションでMediatRライブラリを使用して、メディエーター パターンと CQRS を試してきました。このアプリケーションはバッチ製造プラントで使用され、ユーザーはアクティブなバッチと完了したバッチのリストを表示し、必要に応じてバッチ情報を更新できます。各バッチには、品質やプロセスの測定値など、大量の情報が関連付けられています。データの読み取りと書き込みは、次の記事に基づいてクエリとコマンドにまとめられています。

その間...私のアーキテクチャのクエリ側で

MediatR と AutoMapper を使用した CQRS

クエリとクエリ ハンドラの簡単な例を次に示します。DataContextSimpleInjector を使用してクエリ ハンドラに挿入されます。

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 の有効期間にあります。理想的には、分離されたトランザクションごとに 1 つのインスタンスを使用したいと考えています。この場合、次のようなものが含まれます。

  • データベースからバッチのリストを取得する
  • バッチの品質指標のリストを取得する (これらは別のデータベースに保存され、ストアド プロシージャを介してアクセスされます)
  • データベース内の複数のエンティティの更新を含む場合があるバッチの更新

これにより、DbContext のスコープ付きまたは一時的なライフスタイルに向かうことになります。ただし、一時的なライフスタイルを使用する場合、SimpleInjector は次のエラーを発生させます。これは、次のように型を登録するときにスローされます。

container.Register<DataContext>();

SimpleInjector.dll でタイプ 'SimpleInjector.DiagnosticVerificationException' の未処理の例外が発生しました

追加情報: 構成が無効です。次の診断警告が報告されました。

-[Disposable Transient Component] DataContextはtransientとして登録されていますが、IDisposableを実装しています。

SimpleInjector Web サイトでこの問題を調査すると、次のメモが明らかになります。

警告: 一時インスタンスはコンテナーによって追跡されません。これは、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());

MediatR.dll で 'System.InvalidOperationException' 型の未処理の例外が発生しました

追加情報: タイプ MediatorTest.GetAllBatchesQuery の要求のハンドラーが見つかりませんでした。

コンテナーまたはサービス ロケーターが正しく構成されていないか、コンテナーにハンドラーが登録されていません。

デバッグして MediatR コードを調べると、メソッドが呼び出されると、を呼び出すことによってクラスmediator.Send(...)の新しいインスタンスが作成されることがわかります。ただし、この時点では実行スコープ内にないため、適切に初期化されず、例外が発生する可能性があります。GetAllBatchesQueryHandlercontainer.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;
        }
    }
}
4

1 に答える 1