101

私は常にリポジトリ パターンを使用していましたが、最新のプロジェクトでは、その使用と「作業単位」の実装を完璧にできるかどうかを確認したかったのです。掘り下げるほど、 「本当に必要なのか?」と自問自答するようになりました。

さて、これはすべて Stackoverflow に関するいくつかのコメントから始まり、Ayende Rahien の彼のブログへの投稿へのトレースがあります。

これはおそらく永遠に語り継がれる可能性があり、さまざまなアプリケーションに依存します。私が知りたいこと、

  1. このアプローチは Entity Framework プロジェクトに適していますか?
  2. このアプローチを使用すると、ビジネス ロジックはまだサービス レイヤーまたは拡張メソッドで実行されますか (以下で説明するように、拡張メソッドは NHib セッションを使用しています)。

これは、拡張メソッドを使用して簡単に実行できます。クリーン、シンプル、再利用可能。

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

このアプローチをDI として使用すると、インターフェイスを作成してコントローラーに挿入するNinject必要がありますか?Context

4

9 に答える 9

50

リポジトリ パターンは抽象化です。その目的は、複雑さを軽減し、コードの残りの部分を永続的に無知にすることです。おまけとして、統合テストの代わりに単体テスト書くことができます。

問題は、多くの開発者がパターンの目的を理解できず、永続化固有の情報を呼び出し元に漏らすリポジトリを作成できないことです (通常は を公開することによってIQueryable<T>)。そうすることで、OR/M を直接使用するよりもメリットがありません。

別の回答に対処するための更新

例外のコーディング

リポジトリを使用することは、永続化テクノロジを切り替えることができるということではありません (つまり、データベースを変更したり、代わりに Web サービスを使用したりするなど)。これは、ビジネス ロジックを永続性から分離して、複雑さと結合を減らすことです。

単体テストと統合テスト

リポジトリの単体テストは作成しません。限目。

しかし、リポジトリ (または永続化とビジネスの間の他の抽象化レイヤー) を導入することで、ビジネス ロジックの単体テストを作成できます。つまり、データベースが正しく構成されていないためにテストが失敗することを心配する必要はありません。

クエリについて。LINQ を使用する場合は、リポジトリの場合と同様に、クエリが機能することも確認する必要があります。それは統合テストを使用して行われます。

違いは、ビジネスと LINQ ステートメントを混在させていない場合、失敗しているのは永続化コードであり、他の何かではないことを 100% 確信できることです。

テストを分析すると、問題が混在していない場合 (つまり、LINQ + ビジネス ロジック)、テストがはるかにクリーンであることがわかります。

リポジトリの例

ほとんどの例はでたらめです。それはとても真実です。ただし、デザインパターンをグーグルで検索すると、多くのくだらない例が見つかります。これは、パターンの使用を避ける理由にはなりません。

正しいリポジトリ実装の構築は非常に簡単です。実際、次の 1 つのルールに従うだけで済みます。

必要になる瞬間まで、リポジトリ クラスに何も追加しないでください。

多くのコーダーは怠け者であり、汎用リポジトリを作成し、必要なメソッドが多数ある基本クラスを使用しようとします。ヤグニ。リポジトリ クラスを 1 回作成すると、アプリケーションが存続する限り (数年かかる場合もあります)、それを維持できます。なぜ怠け者でそれをファックします。基本クラスの継承なしでクリーンに保ちます。これにより、読み取りと保守がはるかに簡単になります。

(上記のステートメントはガイドラインであり、法律ではありません。基本クラスは十分に動機付けられる可能性があります。追加する前によく考えて、正しい理由で追加するようにしてください)

古いもの

結論:

ビジネス コードに LINQ ステートメントを使用することや単体テストを気にしないのであれば、Entity Framework を直接使用しない理由はないと思います。

アップデート

私は、リポジトリ パターンと「抽象化」が実際に何を意味するかについてブログを書いています: http://blog.gauffin.org/2013/01/repository-pattern-done-right/

更新 2

20 以上のフィールドを持つ単一のエンティティ タイプの場合、順列の組み合わせをサポートするクエリ メソッドをどのように設計しますか? 名前だけで検索を制限したくありません。ナビゲーション プロパティを使用した検索についてはどうでしょうか。特定の価格コードを持つアイテムを含むすべての注文を一覧表示し、3 レベルのナビゲーション プロパティ検索を行います。発明された全体の理由IQueryableは、データベースに対する検索の任意の組み合わせを構成できるようにすることでした。理論的にはすべてが素晴らしいように見えますが、ユーザーのニーズは理論よりも優先されます。

繰り返しますが、20 以上のフィールドを持つエンティティが正しくモデル化されていません。それは神の存在です。分解してください。

私はそれIQueryableが質問のために作られたものではないと主張しているわけではありません。漏れやすいので、リポジトリパターンのような抽象化レイヤーには適していないと言っています。100% 完全な LINQ To Sql プロバイダー (EF など) はありません。

それらはすべて、eager/lazy loading の使用方法や SQL "IN" ステートメントの実行方法など、実装固有のものを持っています。IQueryableリポジトリに公開すると、ユーザーはそれらすべてを知る必要があります。したがって、データ ソースを抽象化しようとする全体の試みは完全な失敗です。OR/M を直接使用するよりもメリットがなく、複雑さを増すだけです。

リポジトリ パターンを正しく実装するか、まったく使用しないでください。

(本当に大きなエンティティを処理したい場合は、Repository パターンとSpecification パターンを組み合わせることができます。これにより、テスト可能な完全な抽象化が得られます。)

于 2013-07-03T13:23:37.843 に答える
12

まず、いくつかの回答で示唆されているように、EF 自体はリポジトリ パターンです。リポジトリとして名前を付けるためだけに、さらに抽象化を作成する必要はありません。

単体テスト用のモック可能なリポジトリ、本当に必要ですか?

単体テストで EF がテスト DB と通信して、ビジネス ロジックを SQL テスト DB に対して直接テストできるようにします。リポジトリパターンをモックするメリットはまったくありません。テスト データベースに対して単体テストを実行する際に実際に間違っていることは何ですか? 一括操作ができないので、生の SQL を書くことになります。メモリ内の SQLite は、実際のデータベースに対して単体テストを実行するのに最適な候補です。

不必要な抽象化

将来、EF を NHbibernate などに簡単に置き換えることができるように、リポジトリを作成しますか? 素晴らしいプランに思えますが、本当に費用対効果が高いのでしょうか?

Linq は単体テストを殺しますか?

それがどのように殺すことができるかについての例を見たいと思います。

依存性注入、IoC

うわー、これらは素晴らしい言葉です。確かに理論的には素晴らしいように見えますが、優れたデザインと優れたソリューションの間でトレードオフを選択する必要がある場合があります。私たちはそれらすべてを使用しましたが、最終的にはすべてをゴミ箱に捨てて、別のアプローチを選択しました。サイズ vs スピード (コードのサイズと開発のスピード) は、実生活において非常に重要です。ユーザーは柔軟性を必要とし、DI や IoC の観点からコードの設計が優れているかどうかは気にしません。

Visual Studio をビルドしていない場合

Visual Studio や Eclipse のような複雑なプログラムを構築する場合、これらの優れた設計はすべて、多くの人によって開発され、高度にカスタマイズ可能である必要がある場合に必要です。これらの IDE は何年にもわたる開発を経て、すべての優れた開発パターンが具体化され、これらすべての優れた設計パターンが非常に重要な場所で進化してきました。しかし、単純な Web ベースの給与計算や単純なビジネス アプリを実行している場合は、数百万のユーザー向けに展開するだけの数百万のユーザー向けに時間をかけて構築するよりも、時間をかけて開発を進化させる方がよいでしょう。

フィルター ビューとしてのリポジトリ - ISecureRepository

一方、リポジトリは、現在のユーザー/ロールに基づいて必要なフィラーを適用することにより、データへのアクセスを保護する EF のフィルター処理されたビューである必要があります。

しかし、そうすると、管理するコードベースが膨大になるため、リポジトリはさらに複雑になります。人々は、さまざまなユーザー タイプまたはエンティティ タイプの組み合わせに対して、さまざまなリポジトリを作成することになります。これだけでなく、多くの DTO が発生します。

次の回答は、クラスとメソッドのセット全体を作成せずに Filtered Repository を実装した例です。質問に直接答えることはできませんが、質問を導き出すのに役立ちます。

免責事項: 私は Entity REST SDK の作成者です。

http://entityrestsdk.codeplex.com

上記を念頭に置いて、CRUD 操作のフィルターを保持する SecurityContext に基づいて、フィルター処理されたビューのリポジトリを作成する SDK を開発しました。また、複雑な操作を単純化するのは 2 種類のルールだけです。1 つはエンティティへのアクセス、もう 1 つはプロパティの読み取り/書き込みルールです。

利点は、さまざまなユーザー タイプに合わせてビジネス ロジックやリポジトリを書き直す必要がなく、単純にアクセスをブロックまたは許可することです。

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

これらの LINQ ルールは、操作ごとに SaveChanges メソッドでデータベースに対して評価され、これらのルールはデータベースの前でファイアウォールとして機能します。

于 2013-07-08T08:46:24.713 に答える
7

どちらの方法が正しいかについては多くの議論があるので、どちらも許容できると考えているので、最も気に入った方を使用します (これはリポジトリではありません、UoW)。

EF では、UoW は DbContext を介して実装され、DbSet はリポジトリです。

データ レイヤーの操作方法については、DbContext オブジェクトを直接操作するだけです。複雑なクエリについては、再利用できるクエリの拡張メソッドを作成します。

Ayende も、CUD 操作を抽象化することがいかに悪いかについての投稿をいくつか持っていると思います。

私は常にインターフェースを作成し、そこからコンテキストを継承して、DI に IoC コンテナーを使用できるようにします。

于 2013-01-01T13:50:22.517 に答える
3

EF に最も適用されるのは、リポジトリ パターンではありません。これは Facade パターンです (EF メソッドの呼び出しをより単純で使いやすいバージョンに抽象化します)。

EF は、Repository パターン (および Unit of Work パターンも) を適用するものです。つまり、EF はデータ アクセス層を抽象化するものであるため、ユーザーは SQLServer を扱っていることを認識できません。

そして、EF を介したほとんどの「リポジトリ」は、EF の単一のメソッドに非常に直接的にマップするだけであり、同じ署名を持つという点でも、優れた Facade ではありません。

したがって、このいわゆる「リポジトリ」パターンを EF に適用する 2 つの理由は、テストを容易にすることと、それに対する「既定の」呼び出しのサブセットを確立することです。それ自体は悪くありませんが、明らかにリポジトリではありません。

于 2016-05-05T07:09:06.353 に答える
1

現時点でのリポジトリ(またはそれを呼び出すことを選択する) は、主に永続層を抽象化することに関するものです。

クエリ オブジェクトと組み合わせて使用​​するため、アプリケーション内の特定のテクノロジとの結合はありません。また、テストが大幅に容易になります。

だから、私は持っている傾向があります

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

デリゲートとしてコールバックを持つ非同期メソッドを追加する可能性があります。リポジトリは一般的に簡単に実装できるため、アプリからアプリへの実装の行に触れる必要はありません。まあ、これは少なくとも NH を使用する場合に当てはまります。私は EF でもそれを行いましたが、EF が嫌いになりました。4. 会話はトランザクションの開始です。いくつかのクラスがリポジトリ インスタンスを共有している場合は非常にクールです。また、NH の場合、私の実装の 1 つのリポジトリは、最初のリクエストで開かれる 1 つのセッションに相当します。

次に、クエリ オブジェクト

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

私が NH で使用する構成では、ISession を渡すためだけに使用します。EFでは多かれ少なかれ意味がありません。

クエリの例は.. (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

EF クエリを実行するには、セッションではなく、Abstract ベースにコンテキストが必要です。しかし、もちろんifcも同じです。

このようにして、クエリ自体がカプセル化され、簡単にテストできます。何よりも、私のコードはインターフェースのみに依存しています。すべてがとてもきれいです。ドメイン (ビジネス) オブジェクトはまさにそれです。たとえば、テストが困難なアクティブ レコード パターンを使用し、ドメイン オブジェクトにデータ アクセス (クエリ) コードを混在させる場合のように、責任が混在することはありません。自体??)。誰もがデータ転送用の POCO を自由に作成できます。

全体として、このアプローチにより多くのコードの再利用と単純化が提供されますが、私が想像できるものは何も失われません。何か案は?

Ayende のすばらしい投稿と継続的な献身に感謝します。ここにあるのは彼のアイデア (クエリ オブジェクト) であり、私のものではありません。

于 2013-07-03T13:16:09.503 に答える
1

Linq は最近の「リポジトリ」です。

GetXByYISession+Linq は既にリポジトリであり、メソッドも一般化も必要ありませんQueryData(Query q)。DAL の使用にはやや偏執的ですが、私はまだリポジトリ インターフェイスを好みます。(保守性の観点からは、特定のデータ アクセス インターフェースに対するファサードも必要です)。

これは私たちが使用するリポジトリです - これは nhibernate の直接使用から私たちを分離しますが、linq インターフェースを提供します (例外的なケースでは ISession アクセスとして、最終的にリファクタリングの対象となります)。

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}
于 2013-01-02T10:15:24.240 に答える