リポジトリはIQueryableを返す必要がありますか?


66

のインスタンスを返すリポジトリを持つプロジェクトをたくさん見てきましたIQueryable。これにより、追加のフィルターとソートをIQueryable他のコードで実行でき、異なるSQLが生成されます。私はこのパターンがどこから来たのか、それが良いアイデアであるかどうかに興味があります。

私の最大の懸念はIQueryable、データベースが列挙されると、しばらくするとデータベースにヒットするという約束です。これは、エラーがリポジトリの外部にスローされることを意味します。これは、Entity Framework例外がアプリケーションの別のレイヤーでスローされることを意味する場合があります。

また、過去(特にトランザクションを使用する場合)に複数のアクティブな結果セット(MARS)の問題に遭遇しましたが、このアプローチはこれがより頻繁に発生するように聞こえます。

リポジトリコードを終了する前に、常にLINQ式を呼び出すAsEnumerableToArray、各LINQ式の最後でデータベースがヒットすることを確認しました。

IQueryableデータレイヤーのビルディングブロックとして戻ることが役立つかどうか疑問に思っています。あるリポジトリが別のリポジトリを呼び出してさらに大きなIQueryable


遅延クエリの実行時とクエリ構築時の間にデータベースが使用できなかった場合の違いは何ですか?消費コードは、関係なくその状況に対処する必要があります。
スティーブンエバーズ

興味深い質問ですが、おそらく答えるのは難しいでしょう。簡単な検索では、あなたの質問についてかなり白熱した議論が表示されます。duckduckgo.com
コービン

6
すべては、クライアントが遅延実行の恩恵を受けることができるLinq句を追加できるようにするかどうかにかかっています。wheredeferred IQueryableに句を追加する場合、結果セット全体ではなく、そのデータをネットワーク経由で送信するだけで済みます。
ロバートハーベイ

リポジトリパターンを使用している場合-いいえ、IQueryableを返さないでください。良い理由は次のとおりです
アーニスラプサ

1
@SteveEvers大きな違いがあります。リポジトリで例外がスローされた場合、データ層固有の例外タイプでラップできます。後で発生する場合は、元の例外の種類をキャプチャする必要があります。例外の原因に近いほど、その原因を知ることができます。これは、意味のあるエラーメッセージを作成するために重要です。私見、EF固有の例外が私のリポジトリを離れることを許可することは、カプセル化の違反です。
トラビスパークス

回答:


47

IQueryableを返すことにより、リポジトリのコンシューマーに確実に柔軟性を与えることができます。結果を絞り込むという責任をクライアントに委ねます。これは当然、利益と松葉杖の両方になる可能性があります。

良い面として、必要なデータを取得するために(少なくともこのレイヤーで)大量のリポジトリメソッド(GetAllActiveItems、GetAllNonActiveItemsなど)を作成する必要はありません。好みによって、これは良いことも悪いこともあります。あなたはします(/必要があります)あなたの実装が付着行動の契約を定義する必要がありますが、それはどこに行くかはあなた次第です。

そのため、リポジトリの外にザラザラした検索ロジックを配置し、ユーザーが望む方法で使用できるようにすることができます。そのため、IQueryableを公開すると、柔軟性が最も高くなり、インメモリフィルタリングなどと対照的に効率的なクエリが可能になり、特定のデータ取得メソッドを大量に作成する必要性を減らすことができます。

一方、今ではユーザーにショットガンを与えています。彼らはあなたが意図していなかったかもしれないことを行うことができます(.include()の過剰使用、重い重いクエリの実行、それぞれの実装でのメモリ内フィルタリングの実行など)。全権アクセス。

そのため、チーム、彼らの経験、アプリのサイズ、全体的な階層化とアーキテクチャに依存します…それは依存します:-\


作業する場合は、IQueryableを返すことができます。IQueryableはDBを呼び出しますが、階層化と動作のコントロールを追加します。
PSR

1
@psrこれが何を意味するのか説明してもらえますか?
トラビスパーク

1
@TravisParks-DBによってIQueryableを実装する必要はありません。IQueryableクラスを自分で書くことができます-それはかなり難しいです。したがって、データベースIQueryableの周りにIQueryableラッパーを記述し、IQueryableで基礎となるDBへのフルアクセスを制限できます。しかし、これは非常に難しいので、広く行われるとは思わないでしょう。
psr

2
IQueryablesを返さない場合でも、単にExpression<Func<Foo,bool>>メソッドに渡すだけで多くのメソッド(GetAllActiveItems、GetAllNonActiveItemsなど)を作成する必要がないようにすることができます。このパラメーターはWhereメソッドに直接渡すことができるため、コンシューマーはIQueryable <Foo>に直接アクセスする必要なく、フィルターを選択できます。
18

1
@hanzolo:リファクタリングの意味に依存します。階層化された疎結合のコードベースを使用している場合、設計の変更(新しいフィールドの追加やリレーションの変更など)が苦痛であることは事実です。ただし、1つのレイヤーのみを変更する必要がある場合、リファクタリングはそれほど苦労しません。それはすべて、アプリケーションの再設計を期待するか、同じ基本アーキテクチャの実装を単にリファクタリングするかによって異なります。どちらの場合でも疎結合を引き続き推奨しますが、コアドメインの概念を変更するときにリファクタリングに追加されることは間違いありません。
18

29

IQueryableをパブリックインターフェイスに公開することはお勧めできません。その理由は基本的です。言われているように、IQueryableの実現を提供することはできません。

パブリックインターフェイスは、プロバイダーとクライアント間の契約です。そしてほとんどの場合、完全な実装が期待されます。IQueryableはチートです:

  1. IQueryableを実装することはほぼ不可能です。そして現在、適切な解決策はありません。Entity Frameworkでさえ多くのUnsupportedExceptionsを持っています。
  2. 現在、クエリ可能なプロバイダーはインフラストラクチャに大きく依存しています。Sharepointには独自の部分実装があり、My SQLプロバイダーには個別の部分実装があります。

リポジトリパターンは、階層化による明確な表現を提供し、最も重要なのは実現の独立性です。したがって、将来のデータソースを変更できます。

問題は、「データソースの置換の可能性があるべきですか?」です。

明日のデータの一部をSAPサービス、NoSQLデータベース、または単にテキストファイルに移動できる場合、IQueryableインターフェイスの適切な実装を保証できますか?

(これが悪い習慣である理由についてのより良い点)


この答えは、揮発性の外部リンクを調べることなく、単独では成り立ちません。少なくとも外部リソースによって作成された重要なポイントを要約することを検討し、個人的な意見をあなたの答え(「聞いたことのない最悪のアイデア」)から遠ざけるようにしてください。また、IQueryableとは無関係と思われるコードスニペットを削除することも検討してください。
ラースヴィクルンド

1
答えは例と問題の説明で更新され、あなたに@LarsViklundをありがとう
Artru

19

現実的には、遅延実行が必要な場合、3つの選択肢があります。

  • このようにしてください- IQueryable
  • 特定のフィルターまたは「質問」の特定のメソッドを公開する構造を実装します。(GetCustomersInAlaskaWithChildren、以下)
  • 厳密に型指定されたフィルター/ソート/ページAPIを公開し、IQueryable内部で構築する構造を実装します。

私は3番目の方法を好みます(2番目の方法も同僚が実装するのを手伝いました)が、明らかにいくつかのセットアップとメンテナンスが関係しています。(T4が助けに!)

編集:私が話していることを取り巻く混乱を解消するために、IQueryableから盗まれた次の例を考えてみましょう。犬を殺す、妻を盗む、生きる意志を殺すなど

このようなものを公開するシナリオでは:

public class CustomerRepo : IRepo 
{ 
     private DataContext ct; 
     public Customer GetCustomerById(int id) { ... } 
     public Customer[] GetCustomersInAlaskaWithChildren() { ... } 
} 

私が話しているAPIを使用するとGetCustomersInAlaskaWithChildren、柔軟な方法で表現(または他の条件の組み合わせ)できるメソッドを公開でき、リポジトリはそれをIQueryableとして実行し、結果を返します。ただし、重要なことは、リポジトリレイヤー内で実行されるため、遅延実行の利点を活用できることです。結果を取得した後でも、LINQ-to-to-objectsを思う存分使用できます。

このようなアプローチのもう1つの利点は、POCOクラスであるため、このリポジトリがWebまたはWCFサービスの背後に存在できることです。AJAXや、LINQについて最初に知らない他の呼び出し元に公開される可能性があります。


4
.NETの組み込みクエリAPIの代わりにクエリAPIを作成しますか?
マイケルブラウン

4
私にはかなり無意味に
聞こえます-ozz

7
@MikeBrownこの質問のポイント-と私の答えは-あなたが公開したいということです行動利点のをIQueryable 公開することなく、IQueryable自分自身を。組み込みAPIを使用して、どのようにこれを行うのですか?
ギャラクティック

2
それは非常に良いトートロジーです。あなたはそれを避けたい理由を説明していません。Heck WCF Data Servicesは、IQueryableをネットワーク全体に公開します。私はあなたを選ぶつもりはありません。IQueryableの人々に対するこの嫌悪感を理解していないだけです。
マイケルブラウン

2
IQueryableを使用するだけなら、なぜリポジトリを使用するのでしょうか?リポジトリは、実装の詳細を隠すことを目的としています。(また、WCF Data Servicesは、過去4年間にこのようなリポジトリを使用していたほとんどのソリューションよりもかなり新しいため、作成するためにすぐに更新される可能性はほとんどありません。光沢のある新しいおもちゃの使用。)
ギャラクティック

8

正当な答えは1つだけです。それは、リポジトリの使用方法に依存します。

極端な例では、リポジトリはDBContextの非常に薄いラッパーであるため、データベース駆動型アプリの周りにテスト容易性のベニアを注入できます。CRUDアプリはこれを必要としないため、LINQフレンドリDBを使用せずに、これを非接続で使用できるという現実世界の期待はありません。確かに、なぜIQueryableを使用しないのですか?IEnumarableを使用すると、ほとんどの利点が得られ[実行:遅延実行]し、それほど汚く感じないので、おそらくIEnumarableを好むでしょう。

あなたがその極端に座っていない限り、私はリポジトリパターンの精神を利用して、基礎となるデータベース接続の意味を持たない適切な実体化されたコレクションを返そうと努力します。


6
帰国に関してはIEnumerable、それはそれは注意することが重要だ((IEnumerable<T>)query).LastOrDefault()とは非常に異なっているquery.LastOrDefault()(仮定です)。そのため、とにかくすべての要素を反復処理する場合にのみ返すようにしてください。queryIQueryableIEnumerable
ルー

7
 > Should Repositories return IQueryable?

データベースまたは他のIQueryableプロバイダーから隔離してビジネスロジック(=リポジトリ消費者)をテストするためのモックリポジトリを簡単に作成する方法がないため、テスト駆動開発/ユニットテストを実行する場合はNOです。


6

純粋なアーキテクチャーの観点から見ると、IQueryableは漏れやすい抽象化です。一般的に、ユーザーにあまりにも多くのパワーを提供します。そうは言っても、それが理にかなっている場所があります。ODataを使用している場合、IQueryableを使用すると、簡単にフィルター処理、並べ替え、グループ化などが可能なエンドポイントを非常に簡単に提供できます。そのようにして、データを事前にフィルター処理し、IQueryableメソッドのみを使用して、クライアントが結果をカスタマイズできるようにします。


6

私もそのような選択に直面しました。

それでは、プラス面とマイナス面をまとめましょう。

ポジティブ:

  • 柔軟性。クライアント側がたった1つのリポジトリメソッドでカスタムクエリを構築できるようにすることは、間違いなく柔軟で便利です。

マイナス面:

  • テスト不可。(実際には、通常はORMである、基になるIQueryable実装をテストします。ただし、代わりにロジックをテストする必要があります)
  • 責任を曖昧にする。リポジトリのその特定のメソッドが何をするかを言うことは不可能です。実際、それはデータベース全体で好きなものを返すことができる神の方法です
  • 安全でない。このリポジトリメソッドでクエリできるものに制限はありませんが、場合によっては、そのような実装はセキュリティポイントから安全でない可能性があります。
  • キャッシュの問題(または一般的には、前処理または後処理の問題)。たとえば、アプリケーションのすべてをキャッシュすることは一般的に賢明ではありません。何かをキャッシュしようとすると、データベースに大きな負荷がかかります。しかし、リポジトリメソッドが1つしかない場合、そのクライアントがデータベースに対して事実上すべてのクエリを発行するために使用する方法は?
  • ロギングの問題。リポジトリレイヤーで何かをログに記録すると、最初にIQueryableを検査して、この特定の呼び出しがログに記録されるかどうかを確認できます。

このアプローチの使用はお勧めしません。代わりに、いくつかの仕様を作成し、それらを使用してデータベースを照会できるリポジトリメソッドを実装することを検討してください。仕様パターンは、一連の条件をカプセル化する優れた方法です。


5

リポジトリでIQueryableを公開することは、開発の初期段階では完全に受け入れられると思います。IQueryableと直接連携し、並べ替え、フィルタリング、グループ化などを処理できるUIツールがあります。

そうは言っても、レポジトリを汚すだけで1日に電話するのは無責任だと思います。一般的なクエリに対して便利な操作とクエリヘルパーを使用すると、クエリのロジックを集中化しながら、リポジトリのコンシューマーに小さなインターフェイスサーフェスを公開できます。

プロジェクトの最初の数回の反復では、リポジトリでIQueryableを公開することで、より迅速に成長できます。最初の完全なリリースの前に、クエリ操作をプライベートまたは保護し、ワイルドクエリを1つの屋根の下に置くことをお勧めします。


4

私が見たのは(そして私が自分で実装しているのは)次のことです:

public interface IEntity<TKey>
{
    TKey Id { get; set; }
}

public interface IRepository<TEntity, in TKey> where TEntity : IEntity<TKey>
{
    void Create(TEntity entity);
    TEntity Get(TKey key);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> expression);
    void Update(TEntity entity);
    void Delete(TKey id);
}

これができることは、LINQyビジネスを公開せずにIQueryable.Where(a => a.SomeFilter)実行concreteRepo.GetAll(a => a.SomeFilter)することにより、レポジトリでクエリを実行できる柔軟性を持たせることです。

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