12

現在、作業中のリポジトリ パターンを実装しています。すべてのリポジトリは独自のインターフェイスの背後にあり、Ninject を介してマッピングされます。私たちのプロジェクトは非常に大きく、解決しようとしているこのパターンにはいくつかの癖があります。

まず、10 個から 15 個以上のリポジトリがすべて同じコントローラーに必要なコントローラーがいくつかあります。非常に多くのリポジトリを要求すると、コンストラクターはかなり醜くなります。2 番目の癖は、複数のリポジトリでメソッドを呼び出した後に明らかになります。複数のリポジトリで作業を行った後、SaveChanges メソッドを呼び出す必要がありますが、どのリポジトリで呼び出す必要があるでしょうか? すべてのリポジトリに 1 つあります。すべてのリポジトリには、挿入された Entity Framework データ コンテキストの同じインスタンスがあるため、任意のリポジトリをランダムに選択して保存を呼び出すことができます。とても面倒に思えます。

「Unit Of Work」パターンを調べて、両方の問題を解決すると思われる解決策を思いつきましたが、この解決策に 100% の自信があるわけではありません。というクラスを作りましたDataBucket

// Slimmed down for readability
public class DataBucket
{
    private DataContext _dataContext;

    public IReportsRepository ReportRepository { get; set; }
    public IEmployeeRepository EmployeeRepository { get; set; }
    public IDashboardRepository DashboardRepository { get; set; }

    public DataBucket(DataContext dataContext,
        IReportsRepository reportsRepository,
        IEmployeeRepository employeeRepository,
        IDashboardRepository dashboardRepository)
    {
        _dataContext = dataContext;
        this.ReportRepository = reportsRepository;
        this.EmployeeRepository = employeeRepository;
        this.DashboardRepository = dashboardRepository;
    }

    public void SaveChanges()
    {
        _dataContext.SaveChanges();
    }
}

これにより、両方の問題が解決されるようです。データ バケット自体にはメソッドが1 つしかないSaveChangesため、データ バケットという 1 つのオブジェクトだけを挿入します。次に、すべてのリポジトリにプロパティとしてアクセスします。データ バケットは、コンストラクターでリポジトリのすべて (簡単に 50 以上) を受け入れるため、少し乱雑に見えます。

新しいリポジトリを追加するプロセスには、インターフェイスの作成、リポジトリの作成、Ninject でのインターフェイスとリポジトリのマッピング、データ バケットへのプロパティの追加とデータの入力が含まれます。

私は、上からのステップを排除するこれに代わるものを考えました。

public class DataBucket
{
    private DataContext _dataContext;

    public IReportsRepository ReportRepository { get; set; }
    public IEmployeeRepository EmployeeRepository { get; set; }
    public IDashboardRepository DashboardRepository { get; set; }

    public DataBucket(DataContext dataContext)
    {
        _dataContext = dataContext;
        this.ReportRepository = new ReportsRepository(dataContext);
        this.EmployeeRepository = new EmployeeRepository(dataContext);
        this.DashboardRepository = new DashboardRepository(dataContext);
    }

    public void SaveChanges()
    {
        _dataContext.SaveChanges();
    }
}

これにより、Ninject のすべてのリポジトリ マッピングがほとんどなくなります。これは、それらがすべてデータ バケットでインスタンス化されるためです。したがって、新しいリポジトリを追加する手順は次のとおりです。インターフェイスの作成、リポジトリの作成、データ バケットへのプロパティの追加、およびインスタンス化。

このモデルに欠陥は見られますか? 表面的には、この方法でリポジトリを使用する方がはるかに便利に見えます。これは以前に対処された問題ですか?もしそうなら、この問題に対する最も一般的かつ/または最も効率的なアプローチは何ですか?

4

4 に答える 4

6

まず、10 個から 15 個以上のリポジトリがすべて同じコントローラーに必要なコントローラーがいくつかあります。

Abstract factory パターンについて説明します。Ninject にすべてのリポジトリを登録してコントローラーに注入する代わりに、必要なリポジトリを提供できるファクトリの単一の実装を登録します。コントローラーが本当に必要とする場合にのみ、遅延して作成することもできます。ファクトリをコントローラーに注入するよりも。

はい、いくつかの欠点もあります-コントローラーにリポジトリを取得する許可を与えています。それはあなたにとって問題ですか?単一の実装で複数のファクトリ インターフェイスが必要な場合、または単に公開する場合は、いつでも一部のサブシステム用に複数のファクトリを作成できます。まだすべてのケースをカバーしているわけではありませんが、15 個のパラメーターをコンストラクターに渡すよりはましです。ところで。それらのコントローラーを分割してはいけませんか?

注: これはサービス プロバイダーのアンチパターンではありません。

複数のリポジトリで作業を行った後、SaveChanges メソッドを呼び出す必要がありますが、どのリポジトリで呼び出す必要があるでしょうか?

Unit of Work パターンについて説明します。Unit of Work は、アプリケーション内の論理トランザクションです。論理トランザクションからのすべての変更を一緒に保持します。リポジトリは変更の永続化を担当するべきではありません - 作業単位が担当する必要があります。DbContext誰かがリポジトリパターンの実装であると述べました。そうではありません。Unit of Work パターンのDbSet実装であり、Repository パターンの実装です。

必要なのは、コンテキストのインスタンスを保持する中央クラスです。リポジトリはデータを取得するためにコンテキストを必要とするため、コンテキストもリポジトリに渡されますが、中央クラス (作業単位) のみが変更の保存を提供します。たとえば、分離レベルを変更する必要がある場合は、データベース トランザクションも処理できます。

作業単位はどこで処理する必要がありますか? それは、論理操作がどこで調整されるかによって異なります。操作がコントローラーのアクションで直接編成されている場合は、アクションにも作業単位が必要であり、SaveChangesすべての変更が完了したら呼び出す必要があります。

懸念の分離をあまり気にしない場合は、作業単位とファクトリを単一のクラスに結合することもできます。それはあなたに私たちをもたらしますDataBucket

于 2012-08-23T19:43:46.653 に答える
2

この場合、Unit of Work パターンを使用することは絶対に正しいと思います。これにより、すべてのリポジトリでメソッドを使用する必要がなくなるだけでSaveChangesなく、データベース自体ではなくコード内からトランザクションを処理する優れた方法が提供されます。RollbackUOW にメソッドを含めて、例外が発生した場合に、操作によってDataContext.

奇妙な依存関係の問題を防ぐためにできることの 1 つは、関連するリポジトリを独自の作業単位でグループ化することです。(それが意図されている場合) すべてのリポジトリを保持する 1 つの大きな DataBucket を用意するのではありません。各 UOW は、含まれているリポジトリと同じレベルでのみアクセス可能である必要があり、他のリポジトリはおそらく他の UOW 自体に依存すべきではありません (リポジトリは他のリポジトリを使用する必要はありません)。

パターンのさらに純粋主義者になりたい場合は、UOW を構成して、単一の作業単位を表すこともできます。ドメイン内の特定の操作を表すようにそれらを定義し、その操作を完了するために必要なリポジトリを提供します。ドメイン内の複数の操作で使用することが理にかなっている場合は、個々のリポジトリが複数の UOW に存在する可能性があります。

たとえば、 aにPlaceCustomerOrderUnitOfWorkCustomerRepositoryOrderRepositoryBillingRepository、およびShippingRepository

CreateCustomerUnitOfWorkだけが必要な場合がありますCustomerRepository。いずれにせよ、その依存関係をそのコンシューマーに簡単に渡すことができます。UOW のよりきめ細かいインターフェイスは、テストの対象を絞り込み、モックを作成する労力を軽減するのに役立ちます。

于 2012-08-23T18:49:02.230 に答える
1

それを呼び出すとすべてが保存されるため、すべてのリポジトリに があるという概念にSaveChangesは欠陥があります。の一部を変更することはできませんDataContext。常にすべてを保存します。そのため、中央のDataContextホルダー クラスを使用することをお勧めします。

または、任意のエンティティ タイプ ( GetTable<T>Query<T>、...) で操作できる汎用メソッドを含むリポジトリを用意することもできます。これにより、これらすべてのクラスが削除され、1 つにマージされます (基本的には、クラスのみDataBucketが残ります)。

リポジトリがまったく必要ない場合もありますDataContext。それ自体を注入できます。はDataContext、それ自体リポジトリであり、本格的なデータ アクセス レイヤーです。ただし、嘲笑には向いていません。

これができるかどうかは、「リポジトリ」が提供する必要があるものによって異なります。


そのクラスを持つことの唯一の問題はDataBucket、このクラスがすべてのエンティティとすべてのリポジトリについて知る必要があることです。そのため、ソフトウェア スタックの非常に高い位置にあります (一番上)。同時に、基本的にすべてに使用されているため、最下位にも位置しています。待って!これは、コードベース全体にわたる依存サイクルです。

これは、それを使用するすべてのものと、それによって使用されるすべてのものを同じアセンブリに配置する必要があることを意味します。

于 2012-08-23T17:37:29.697 に答える
0

私が過去に行ったことは、子インジェクション コンテナーを作成し (Unity を使用していました)、データ コンテキストをContainerControlledLifetime. そのため、リポジトリがインスタンス化されると、常に同じデータ コンテキストが注入されます。次に、そのデータ コンテキストを保持し、「作業単位」が完了したら、DataContext.SaveChanges()すべての変更をデータベースにフラッシュするように呼び出します。

これには、(EF を使用した) いくつかのローカル キャッシングなどの他の利点があり、複数のリポジトリが同じエンティティを取得する必要がある場合、最初のリポジトリのみが実際にデータベース ラウンド トリップを引き起こします。

これは、変更を「バッチ処理」して、単一のアトミック トランザクションとして実行することを確認するための優れた方法でもあります。

于 2012-08-23T17:41:41.620 に答える