Entity Frameworkでリポジトリパターンを使用しないのはなぜですか?


203

就職の面接中に、私はリポジトリパターンがEntity FrameworkのようなORMと連携するのに適したパターンではない理由を説明するように求められました。これはなぜですか?


60
それはトリックの質問だった
オム

2
マイクロソフトは、エンティティフレームワークのデモ中にMicrosoftがリポジトリパターンを非常に頻繁に使用しているとインタビュアーに答えたでしょう。。
ローランブルゴーロイ

1
では、面接官が良いアイデアではなかった理由は何ですか?
ボブ・ホーン

3
面白い事実は、Googleで「リポジトリパターン」を検索すると、主にEntity FrameworkとEFでのパターンの使用方法に関連する結果が得られることです。
アルセニムルゼンコ

2
ayendeのブログayende.com/blogを確認してください。私が知っていることに基づいて、彼はリポジトリパターンを使用していましたが、最終的にはクエリオブジェクトパターンを支持してin
めました

回答:


99

RepositoryパターンがEntity Frameworkで機能しない理由は見当たりません。リポジトリパターンは、データアクセス層に配置する抽象化層です。データアクセス層は、純粋なADO.NETストアドプロシージャからEntity FrameworkまたはXMLファイルまで何でもかまいません。

さまざまなソース(データベース/ XML / Webサービス)からのデータがある大規模システムでは、抽象化レイヤーを使用すると便利です。このシナリオでは、リポジトリパターンが適切に機能します。Entity Frameworkは、舞台裏で行われていることを隠すのに十分な抽象化だとは思いません。

Entity FrameworkのRepositoryパターンをデータアクセスレイヤーメソッドとして使用しましたが、まだ問題に直面しています。

DbContextリポジトリで抽象化するもう1つの利点は、単体テスト可能性です。IRepositoryインターフェースには2つの実装があります。1つ(実際のリポジトリ)DbContextはデータベースとの通信に使用し、2つ目はFakeRepositoryメモリ内オブジェクト/模擬データを返すことができます。これにより、IRepository単体テストが可能になるため、を使用するコードの他の部分がテスト可能になりますIRepository

public interface IRepository
{
  IEnumerable<CustomerDto> GetCustomers();
}
public EFRepository : IRepository
{
  private YourDbContext db;
  private EFRepository()
  {
    db = new YourDbContext();
  }
  public IEnumerable<CustomerDto> GetCustomers()
  {
    return db.Customers.Select(f=>new CustomerDto { Id=f.Id, Name =f.Name}).ToList();
  }
}
public MockRepository : IRepository
{
  public IEnumerable<CustomerDto> GetCustomers()
  {
    // to do : return a mock list of Customers
    // Or you may even use a mocking framework like Moq
  }
}

DIを使用して、実装を取得します

public class SomeService
{
  IRepository repo;
  public SomeService(IRepository repo)
  {
     this.repo = repo;
  }  
  public void SomeMethod()
  {
    //use this.repo as needed
  }    
}

3
私はそれが機能しないとは言いませんでした、私はEFでリポジトリパターンを扱っていますが、今日はデータベースでパターンを使用するのはなぜ良くないのかと尋ねられました

2
[OK]を、これは私が正しい回答として、それを選んだのだろう最も人気のある答えであるので、

65
最も人気のある==が最後だったのはいつですか?
HDave

14
DbContextはすでにリポジトリであり、リポジトリは低レベルの抽象化を目的としています。さまざまなデータソースを抽象化する場合は、それらを表すオブジェクトを作成します。
ダニエルリトル

7
ColacX。コントローラーレイヤーにあるDBcontextだけを試してみましたが、レポパターンに戻ります。Repoパターンでは、ユニットテストは、常に失敗する大規模なDbContextモックから始まりました。EFは使いにくく、EFニュアンスの研究には時間がかかり、時間もかかりました。レポの小さなシンプルなモックができました。コードはきれいです。作業の分離はより明確です。私は、EFが既にレポパターンであり、既にユニットテスト可能であるという群衆に同意しません。
-Rhyous

435

Entity Frameworkでリポジトリパターンを使用しない唯一の最良の理由は?Entity Frameworkはすでにリポジトリパターンを実装しています。DbContextUoW(作業単位)であり、それぞれDbSetがリポジトリです。この上に別のレイヤーを実装すると、冗長になるだけでなく、メンテナンスが難しくなります。

人々はパターンの目的を理解せずにパターンに従います。リポジトリパターンの場合、目的は低レベルのデータベースクエリロジックを抽象化することです。コードにSQLステートメントを実際に記述する昔、リポジトリパターンは、コードベース全体に散在する個々のメソッドからそのSQLを移動し、1か所でローカライズする方法でした。Entity FrameworkやNHibernateなどのORMを使用することは、このコード抽象化の代わりになるため、パターンの必要性がなくなります。

ただし、ORMの上に抽象化を作成することは悪い考えではなく、UoW / repostitoryほど複雑なものではありません。データがEntity Framework、NHibernate、またはWeb APIからのものかどうかを知らず、気にせずにアプリケーションが使用できるAPIを構築するサービスパターンを使用します。サービスクラスにメソッドを追加するだけでアプリケーションに必要なデータが返されるため、これははるかに簡単です。たとえば、To-doアプリを書いている場合、今週中に期限切れでまだ完了していないアイテムを返すサービスコールがあります。アプリが知っているのは、この情報が必要な場合、そのメソッドを呼び出すことだけです。そのメソッド内および一般的なサービスでは、Entity Frameworkまたは使用している他のものとやり取りします。その後、後でORMを切り替えるか、Web APIから情報を取得することにした場合、

これはリポジトリパターンを使用するための潜在的な議論のように聞こえるかもしれませんが、ここでの重要な違いは、サービスがより薄いレイヤーであり、倉庫。


68
これが唯一の正しい答えのようです。
マイクチェンバレン14年

10
EF6 +でモックできますDbContextmsdn.microsoft.com/en-us/data/dn314429.aspxを参照)。それよりも低いバージョンでも、iterfaceを実装しているため、モックされたsで偽のDbContextようなクラスを使用できます。DbSetDbSetIDbSet
クリスプラット14年

14
@TheZenker、あなたはリポジトリパターンに正確に従っていないかもしれません。最も厳密な違いは戻り値です。リポジトリはクエリ可能を返しますが、サービスは列挙可能を返す必要があります。そこにはいくつかの重複があるので、それでも実際にはそれほど白黒ではありません。それはあなたがそれをどのように使用するかにあります。リポジトリは、すべてのオブジェクトのセットを返すだけで、その後さらにクエリを実行する必要がありますが、サービスは最終データセットを返す必要があり、それ以上のクエリはサポートしません。
クリスプラット14年

10
利己的に聞こえるリスクがある:彼らは間違っている。今、公式のチュートリアルに関する限り、MicrosoftはEF6以来私が見たものからリポジトリを使用することをやめました。本に関しては、著者がリポジトリを使用することを選択した理由について話すことはできません。大規模なアプリケーションを構築するトレンチの誰かとして話すことができるのは、Entity Frameworkでリポジトリパターンを使用することはメンテナンスの悪夢であるということです。一握りのリポジトリよりも複雑なものに移ると、リポジトリ/作業単位の管理に膨大な時間を費やすことになります。
クリスプラット14年

6
通常、データベースまたはアクセス方法ごとに1つのサービスのみがあります。ジェネリックメソッドを使用して、同じメソッドセットから複数のエンティティタイプをクエリします。Ninjectを使用してコンテキストをサービスに挿入し、次にサービスをコントローラーに挿入して、すべてが整頓されているようにします。
クリスプラット

45

Ayende Rahien:運命の穴での建築:リポジトリ抽象化レイヤーの悪

彼の結論に同意するかどうかはまだわかりません。それはキャッチ-22です-一方では、クエリ固有のデータ取得方法でタイプ固有のリポジ​​トリにEFコンテキストをラップすると、実際にはコードで(ある種の)ユニットテストを行うことができますが、これはエンティティではほとんど不可能ですフレームワークのみ。一方、リレーションシップの豊富なクエリとセマンティックメンテナンスを実行する機能を失います(ただし、これらの機能に完全にアクセスできる場合でも、EFや他のORMの周りの卵の殻を歩いているように常に感じます、IQueryable実装がサポートしているメソッドとサポートしていないメソッド、ナビゲーションプロパティコレクションへの追加を作成または単なる関連付けとして解釈するかどうか、遅延ロードまたはイージーロードするかどうかデフォルトなど 多分これは良いことです。ゼロインピーダンスのオブジェクトリレーショナル「マッピング」は神話上の生き物のようなものです。おそらく、Entity Frameworkの最新リリースが「マジックユニコーン」とコードネームされた理由でしょう。

ただし、クエリ固有のデータ取得方法を使用してエンティティを取得すると、ユニットテストは基本的にホワイトボックステストになり、テスト対象のユニットがどのリポジトリメソッドを使用するかを正確に事前に知る必要があるため、この問題に選択肢はありませんそれをモックするために呼び出します。また、統合テストも作成しない限り、クエリ自体を実際にテストしているわけではありません。

これらは複雑なソリューションを必要とする複雑な問題です。すべてのエンティティは、それらの間に関係のない個別のタイプであり、それぞれを独自のリポジトリにアトマイズするふりをするだけでは修正できません。できます、それはひどいです。

更新: Entity FrameworkのEffortプロバイダーを使用して、ある程度成功しました。Effortは、実際のデータベースに対して使用するのとまったく同じ方法でEFをテストで使用できるようにするインメモリプロバイダー(オープンソース)です。私はこのプロバイダーを使用するために取り組んでいるこのプロジェクトのすべてのテストを切り替えることを検討しています。それは私がこれまでに見つけた唯一の解決策であり、以前に暴言していたすべての問題に対処しています。唯一のことは、メモリ内データベースを作成するため、テストを開始するときにわずかな遅延があることです(NMemoryと呼ばれる別のパッケージを使用してこれを行います)が、これは本当の問題とは思わない。テストにEffort(SQL CEに対して)を使用することについて説明しているコードプロジェクトの記事があります。


3
単体テストに言及しないアーキテクチャの記事は、自動的にごみ箱に送られます。リポジトリパターンのポイントの1つは、ある程度のテスト能力を獲得することです。
スリーパースミス

3
EFコンテキスト(既にリポジトリである)をラップせずに単体テストを実行できます。データベースクエリではなく、ドメイン/サービスの単体テストを行う必要があります(統合テストです)。
ダニエルリトル

2
EFのテスト容易性はバージョン6で大幅に改善されましたDbContext。今では完全にモックできます。とにかく、いつでもモックすることができますDbSet。とにかく、それがEntity Frameworkの中核です。プロパティ(リポジトリ)を1つの場所(作業単位)に格納DbContextするクラスに過ぎませんDbSet。特に、すべてのデータベースの初期化と接続が必要とされないユニットテストコンテキストでは特にそうです。
クリスプラット14年

関連するエンティティナビゲーションを失うことは悪いことであり、OOPに反しますが、クエリ対象をより詳細に制御できます。
アリレザ

テストの段階まで、EF Coreは、すぐに使用できるインメモリおよびSqliteプロバイダーを備えたインメモリを使用して、単体テストを可能にしました。コンテナー化されたデータベースでテストを実行するために統合テストが必要な場合は、Dockerを使用します。
Sudhanshu Mishra

16

おそらくそれを行う理由は、それが少し冗長だからです。Entity Frameworkは、豊富なコーディングと機能的な利点を提供します。そのため、それを使用する理由は、それを利用して、それらの利点を捨てているリポジトリパターンにラップする場合、他のデータアクセスレイヤーを使用することもできます。


「Entity Frameworkが豊富なコーディングと機能の利点を提供します」の利点を教えてください。
マニラSS

2
これが彼の意味です。var id = Entity.Where(i => i.Id == 1337).Single()カプセル化され、リポジトリにこれをラップします。外部からこのようなクエリロジックを実行することは基本的にできません。 IDを取得するためのリポジトリとインターフェイス。Bはリポジトリからエンティティコンテキストを返すので、クエリロジックを書くことができます(これはナンセンスです)
ColacX

14

理論的には、データベース接続ロジックをカプセル化してより簡単に再利用できるようにすることは理にかなっていると思いますが、以下のリンクが主張するように、現在のフレームワークは基本的にこれを処理します。

リポジトリパターンの再検討


私はこの記事が好きでしたが、エンタープライズアプリの私見、DALとBlの間の抽象化レイヤーはfeatureを持っている必要があります。明日何が使われるのか正確に知ることができなかったからです。しかし、リンクを共有してくれてありがとう

1
個人的にいる間、私はそれが例えばNHibernateのために本当だと思う(ISessionFactoryISession簡単にmockableある)、それが持つことは簡単ではありませんDbContext、残念ながら...
はPatrykĆwiek

6

リポジトリパターンを使用する非常に良い理由は、System.Data.EntityからビジネスロジックやUIを分離できるようにすることです。これには、FakesまたはMocksの使用を許可することによる単体テストでの実際の利点など、多くの利点があります。


私はこの答えに同意します。私のリポジトリは基本的に単なる拡張メソッドであり、式ツリーを構築するだけです。dbcontextの上に直接汎用機能を単純に提供する非常に単純な抽象化の上。抽象化の唯一の本当の目的は、IoCを少し簡単にすることです。リポジトリ内ですべきでないことをやろうとしていると思います。エンティティごとにレポジトリを作成するか、サービスレイヤーにあるべきビジネスロジックをそこに配置します。実際に必要なのは、1つの単純な汎用リポジトリーだけです。その必要はなく、一貫したインターフェースを提供するだけです。
ブランドン

もう1つ追加したいことがあります。はい、CQRSはMOSTの場合に非常に優れた方法です。データベース担当者が開発者とうまく機能しない場合(特に銀行で考えられるよりも頻繁に発生する)、私が働いていた一部のクライアントでは、EF over SQLが最適なオプションです。その特定のシナリオでは、データベースをまったく制御できない場合、リポジトリパターンが意味を持ちます。なぜなら、データ構造によく似ており、データベースに起こっていることを簡単に翻訳でき、その逆も簡単だからです。私の意見では、それは本当に政治的および物流上の決定です。DB神をなだめるため。
ブランドン

1
私は実際、これに関する私の以前の意見に疑問を持ち始めています。EFは、作業単位とリポジトリを組み合わせたパターンです。クリス・プラットがEF6で前述したように、ContextおよびDbSetオブジェクトを簡単にモックできます。実際のデータアクセスメカニズムからビジネスロジッククラスを保護するために、データアクセスをクラスでラップする必要があると思いますが、全体を独り占めしてEFを別のリポジトリでラップし、作業単位の抽象化はやり過ぎのようです。
ジェームズカルショー

あなたの支持声明は、1つだけをリストしている間に多くの利点があるというだけなので、これは良い答えではないと思います。あなたが行うものは、エンティティにインメモリデータベースを使用できるので、正当な理由ではありません単体テスト。
ジョエルマクベス

@jcmcbeth直上のコメントを見ると、リポジトリパターンとEFに関して元の意見が変わっていることがわかります。
ジェームズカルショー

0

タイプごとにリポジトリをnew()するIoCコンテナー(たとえば、それぞれDBContextから独自のIDbSetを呼び出すUserRepositoryおよびGroupRepositoryインスタンス)がリクエストごとに複数のコンテキストを引き起こす可能性がある場合、重複するが異なるEntity Framework DbContextインスタンスで問題が発生しました(MVC / Webコンテキスト内)。

ほとんどの場合はまだ機能しますが、その上にサービスレイヤーを追加し、それらのサービスが1つのコンテキストで作成されたオブジェクトが別のコンテキストの新しいオブジェクトに子コレクションとして正しくアタッチされると想定すると、失敗する場合がありますコミットの速度に依存します。


私はいくつかの異なるプロジェクトでこの問題に遭遇しました。
ColacX

0

小さなプロジェクトでリポジトリパターンを試した後、使用しないことを強くお勧めします。それはシステムを複雑にするからではなく、データのモックが悪夢だからではなく、テストが役に立たなくなるからです!!

データのモックにより、ヘッダーなしで詳細を追加したり、データベースの制約に違反するレコードを追加したり、データベースが削除を拒否するエンティティを削除したりできます。現実の世界では、1回の更新が複数のテーブル、ログ、履歴、概要など、および最終変更日フィールド、自動生成キー、計算フィールドなどの列に影響する場合があります。

要するに、実際のデータベースでテストを実行すると、実際の結果が得られ、サービスとインターフェイスだけでなく、データベースの動作もテストできます。ストアドプロシージャがデータに対して正しい処理を実行するか、期待される結果を返すか、削除するために送信したレコードが本当に削除されたかを確認できます。このようなテストは、ストアドプロシージャからエラーを発生させることを忘れるなどの問題や、数千のそのようなシナリオも明らかにする可能性があります。

エンティティフレームワークは、これまでに読んだ記事のどれよりもリポジトリパターンを実装しており、彼らが達成しようとしていることをはるかに超えていると思います。

リポジトリは、XBase、AdoX、およびAdo.Netを使用していて、エンティティを使用していた当時のベストプラクティスでした!! (リポジトリ上のリポジトリ)

最後に、リポジトリパターンの学習と実装に多くの時間を費やしている人が多すぎると思います。主に、彼らが自分の時間を無駄にしなかったことを自分自身に証明するために。


1
単体テストではデータベースの動作をテストしたくないことを除いて、それは完全にそのレベルのテストではありません。
マリウスジャム

はい、あなたがここで話しているのは統合テストであり、それは確かに価値がありますが、単体テストはまったく異なります。単体テストが実際のデータベースにヒットすることはありませんが、統合テストを追加することをお勧めします。
クリスプラット

-3

移行のため:接続文字列がweb.configにあるため、移行を動作させることはできません。ただし、DbContextはリポジトリ層にあります。IDbContextFactoryには、データベースへの構成文字列が必要です。ただし、移行がweb.configから接続文字列を取得する方法はありません。

回避策はありますが、これに対するクリーンなソリューションはまだ見つかりませんでした!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.