64

最近、ASP.NET MVC を学びました (大好きです)。私は、依存性注入を使用して各要求でリポジトリ インスタンスをロードする会社と協力しており、そのリポジトリの使用に精通しています。

しかし今、私は自分自身の MVC アプリケーションをいくつか書いています。私の会社が使用するリポジトリの方法と理由を完全には理解していません。また、データ アクセスを実装する最善の方法を決定しようとしています。

私は C# と Entity Framework (すべての最新バージョン) を使用しています。

データ アクセスを処理するための 3 つの一般的なアプローチが見られます。

  1. データにアクセスするたびに、using ステートメント内の通常の DB コンテキスト。これは簡単で、問題なく動作します。ただし、2 つの場所が 1 つの要求内で同じデータを読み取る必要がある場合は、データを 2 回読み取る必要があります。(リクエストごとに単一のリポジトリを使用すると、両方の場所で同じインスタンスが使用され、2 回目の読み取りでは最初の読み取りのデータが返されるだけであることがわかります。)

  2. 典型的なリポジトリ パターン。理由はわかりませんが、この典型的なパターンには、データベースから使用されるすべてのテーブルのラッパー クラスを作成することが含まれます。それは私には間違っているようです。実際、これらはインターフェイスとしても実装されているため、技術的には、テーブルごとに 2 つのラッパー クラスを作成することになります。EF がテーブルを作成してくれます。このアプローチが理にかなっているとは思えません。

  3. すべてのエンティティ オブジェクトを提供するために単一のリポジトリ クラスが作成される汎用リポジトリ パターンもあります。これは私にとってはるかに理にかなっています。しかし、それは他の人にとって意味がありますか?上記のリンクは最良のアプローチですか?

このトピックについて他の人から意見をもらいたいです。上記のいずれかを使用して独自のリポジトリを作成していますか、それともまったく別のことを行っていますか。共有してください。

4

4 に答える 4

35

#2 と #3 のブレンドを使用しましたが、可能であれば厳密な汎用リポジトリを好みます (#3 のリンクで提案されているよりも厳密です)。#1 は、単体テストでうまく機能しないため、役に立ちません。

ドメインが小さい場合、またはドメインでクエリを許可するエンティティを制限する必要がある場合は、汎用リポジトリを実装するエンティティ固有のリポジ​​トリ インターフェイスを定義する #2 または #3 が理にかなっていると思います。ただし、クエリを実行するすべてのエンティティに対してインターフェイスと具体的な実装を作成するのは面倒で不要であることがわかりました。public interface IFooRepository : IRepository<Foo>(開発者を許可された集約ルートのセットに制限する必要がない限り)何が良いのでしょうか?

AddRemoveGetGetDeferredCount、およびFindメソッド (Find はIQueryableLINQ を許可するインターフェイスを返します) を使用して汎用リポジトリ インターフェイスを定義し、具体的な汎用実装を作成して、1 日で終了します。Find私はLINQに大きく依存しています。特定のクエリを複数回使用する必要がある場合は、拡張メソッドを使用し、LINQ を使用してクエリを記述します。

これは、永続化のニーズの 95% をカバーします。一般的に実行できないある種の永続化アクションを実行する必要がある場合は、自家製のICommandAPI を使用します。たとえば、NHibernate を使用していて、ドメインの一部として複雑なクエリを実行する必要がある、または一括コマンドを実行する必要があるとします。API はおおよそ次のようになります。

// marker interface, mainly used as a generic constraint
public interface ICommand
{
}

// commands that return no result, or a non-query
public interface ICommandNoResult : ICommand
{
   void Execute();
}

// commands that return a result, either a scalar value or record set
public interface ICommandWithResult<TResult> : ICommand
{
   TResult Execute();
}

// a query command that executes a record set and returns the resulting entities as an enumeration.
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>>
{
    int Count();
}

// used to create commands at runtime, looking up registered commands in an IoC container or service locator
public interface ICommandFactory
{
   TCommand Create<TCommand>() where TCommand : ICommand;
}

これで、特定のコマンドを表すインターフェイスを作成できます。

public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
    Decimal MinimumBalance { get; set; }
}

具体的な実装を作成し、未加工の SQL、NHibernate HQL などを使用して、それをサービス ロケーターに登録できます。

今、私のビジネスロジックでは、次のようなことができます:

var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;

var overdueAccounts = query.Execute();

また、何百万もの紛らわしいプロパティを備えたインターフェイスを使用するのではなく、仕様パターンを使用して、IQuery意味のあるユーザー入力駆動型のクエリを構築することもできますが、それは、仕様パターン自体が混乱を招くとは思わないことを前提としています;)。

パズルの最後のピースは、リポジトリが特定の前後のリポジトリ操作を実行する必要がある場合です。これで、特定のエンティティの汎用リポジトリの実装を非常に簡単に作成し、関連するメソッドをオーバーライドして必要な処理を実行し、IoC またはサービス ロケーターの登録を更新して完了できます。

ただし、このロジックは分野横断的であり、リポジトリ メソッドをオーバーライドして実装するのが面倒な場合があります。そこでIRepositoryBehavior、基本的にはイベント シンクである を作成しました。(以下は私の頭の上からの大まかな定義です)

public interface IRepositoryBehavior
{
    void OnAdding(CancellableBehaviorContext context);
    void OnAdd(BehaviorContext context);

    void OnGetting(CancellableBehaviorContext context);
    void OnGet(BehaviorContext context);

    void OnRemoving(CancellableBehaviorContext context);
    void OnRemove(BehaviorContext context);

    void OnFinding(CancellableBehaviorContext context);
    void OnFind(BehaviorContext context);

    bool AppliesToEntityType(Type entityType);
}

さて、これらの動作は何でもかまいません。監査、セキュリティ チェック、ソフト削除、ドメイン制約の適用、検証など。ビヘイビアを作成し、それを IoC またはサービス ロケータに登録し、汎用リポジトリを変更して登録済みIRepositoryBehaviorのコレクションを取り込み、各ビヘイビアをチェックします。現在のリポジトリ タイプと、適用可能な各動作の事前/事後ハンドラーで操作をラップします。

論理的な削除の動作の例を次に示します (論理的な削除とは、誰かがエンティティを削除するように要求したときに、エンティティを削除済みとしてマークするだけで、再度返すことはできませんが、実際に物理的に削除されることはありません)。

public SoftDeleteBehavior : IRepositoryBehavior
{
   // omitted

   public bool AppliesToEntityType(Type entityType)
   {
       // check to see if type supports soft deleting
       return true;
   }

   public void OnRemoving(CancellableBehaviorContext context)
   {
        var entity = context.Entity as ISoftDeletable;
        entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated

        context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity.
   }
}

はい、これは基本的に NHibernate のイベント リスナーの単純化および抽象化された実装ですが、それが私が気に入っている理由です。A) NHibernate を使わずに振る舞いを単体テストできる B) これらの振る舞いを NHibernate の外部で使用できる (たとえば、リポジトリは REST サービス呼び出しをラップするクライアント実装である) C) NH のイベント リスナーは本当に厄介な問題になる可能性がある;)

于 2012-06-07T04:04:15.027 に答える
12

いくつかの注意点を付けて、1番をお勧めします。番号2は最も一般的であるように思われますが、私の経験では、リポジトリはクエリの厄介なダンプグラウンドになってしまいます。汎用リポジトリー(2)を使用する場合、それはDBContextの単なる薄いラッパーであり、ORMの変更を計画していない限り(悪い考え)、実際には少し無意味です。

ただし、DBContextに直接アクセスする場合は、パイプとフィルターのパターンを使用して、次のような共通ロジックを再利用できるようにします。

items = DBContext.Clients
    .ByPhoneNumber('1234%')
    .ByOrganisation(134);

ByPhoneNumberとByOrganizationは単なる拡張メソッドです。

于 2012-06-07T03:37:30.650 に答える