IQueryable
のインスタンスを返すリポジトリを持つプロジェクトがたくさんあります。これにより、追加のフィルターが可能になり、他のコードによってIQueryable
でソートを実行できます。これにより、生成される別のSQLに変換されます。私はこのパターンがどこから来たのか、それが良いアイデアであるかどうかに興味があります。
私の最大の懸念は、IQueryable
が列挙された後、しばらくしてデータベースにアクセスすることを約束することです。つまり、リポジトリの外部でエラーがスローされます。これは、Entity Framework例外がアプリケーションの別のレイヤーでスローされることを意味する場合があります。
また、過去(特にトランザクションを使用する場合)に複数のアクティブな結果セット(MARS)の問題に遭遇しました。このアプローチは、これがより頻繁に発生するように思われます。
私は常にLINQ式の最後にAsEnumerable
またはToArray
を呼び出して、リポジトリコードを終了する前にデータベースがヒットすることを確認しています。
IQueryable
を返すことがデータレイヤーの構成要素として役立つかどうか疑問に思っています。 1つのリポジトリが別のリポジトリを呼び出してさらに大きなIQueryable
を構築する、かなり贅沢なコードをいくつか見ました。
IQueryableを返すと、リポジトリのコンシューマーは確実に柔軟性が高まります。それはクライアントに結果を絞り込む責任を負わせます、それは当然利益と松葉杖の両方になることができます。
良い面としては、必要なデータを取得するために、(少なくともこのレイヤーで)大量のリポジトリメソッド(GetAllActiveItems、GetAllNonActiveItemsなど)を作成する必要はありません。好みに応じて、これも良いか悪いかです。 will(/ should)は、実装が準拠する動作規約を定義する必要がありますが、それをどこで行うかはあなた次第です。
したがって、ザラザラした検索ロジックをリポジトリの外に置き、ユーザーが望む方法でそれを使用できるようにすることができます。したがって、IQueryableを公開すると、最も柔軟性が高くなり、メモリ内のフィルタリングなどとは対照的に効率的なクエリが可能になります。couldは、大量の特定のデータ取得方法。
一方、ユーザーにはショットガンを与えました。彼らはあなたが意図していないかもしれないことをすることができます(.include()を使いすぎ、重い重いクエリを実行し、インメモリフィルタリングをそれぞれの実装で実行します、など)。これは、完全なアクセス権を付与しているため、基本的にレイヤリングと動作の制御を回避します。
したがって、チーム、経験、アプリのサイズ、全体的な階層化とアーキテクチャなどによって異なります。
IQueryableをパブリックインターフェイスに公開することはお勧めできません。その理由は根本的なものです。つまり、IQueryableの実現は提供されているとは言えないためです。
パブリックインターフェイスは、プロバイダーとクライアント間の契約です。そして、ほとんどの場合、完全な実装が期待されます。 IQueryableはチートです:
リポジトリパターン は、階層化による明確な表現を提供し、最も重要なのは実現の独立性です。したがって、将来のデータソースで変更することができます。
問題は、「データソースの置換の可能性はありますか?」です。
データの明日の一部をSAPサービス、NoSQLデータベース、または単にテキストファイルに移動できる場合、IQueryableインターフェイスの適切な実装を保証できますか?
( more これが悪い習慣である理由についての良い点)
現実的に、遅延実行が必要な場合は、次の3つの選択肢があります。
IQueryable
を公開します。GetCustomersInAlaskaWithChildren
、以下)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-objectsを使用してコンテンツを心に留めることができます。
このようなアプローチのもう1つの利点は、POCOクラスであるため、このリポジトリはWebまたはWCFサービスの背後に存在できることです。 AJAXまたはLINQの最初のことを知らない他の呼び出し元に公開される可能性があります。
正解は1つだけです。リポジトリの使用方法によって異なります。
極端な例として、リポジトリはDBContextの非常に薄いラッパーであるため、データベース駆動型アプリの周りにテスト容易性のベニアを注入できます。 CRUDアプリがこれを必要としないため、これがLINQ対応のDBなしで非接続で使用される可能性があるという現実の期待は実際にありません。それなら、なぜIQueryableを使用しないのですか? IEnumarableをお勧めします。ほとんどの利点が得られ[読み取り:実行の遅延]、汚く感じないからです。
あなたがその極端なところに座っていない限り、私はリポジトリパターンの精神を利用して、基になるデータベース接続の意味を持たない適切なマテリアライズドコレクションを返すように努力します。
> Should Repositories return IQueryable?
[〜#〜] no [〜#〜]ビジネスロジック(=レポジトリ-コンシューマー)を分離してテストするためのモックリポジトリを作成する簡単な方法がないため、テスト駆動型の開発/単体テストを行う場合データベースまたは他のIQueryableプロバイダーから。
私もそのような選択に直面しました。
だから、正と負の側面を要約しましょう:
ポジティブ:
ネガ:
このアプローチを使用しないことをお勧めします。代わりに、いくつかの Specifications を作成し、それらを使用してデータベースにクエリを実行できるリポジトリメソッドを実装することを検討してください。 指定パターン は、一連の条件をカプセル化する優れた方法です。
純粋なアーキテクチャの観点から見ると、IQueryableは漏洩しやすい抽象化です。一般的に、それはユーザーに余りにも多くの力を提供します。そうは言っても、それが理にかなっている場所があります。 ODataを使用している場合、IQueryableを使用すると、簡単にフィルタリング、並べ替え、グループ化できるエンドポイントを非常に簡単に提供できます。実際には、エンティティがマップするDTOを作成し、代わりにIQueryableがDTOを返します。このようにして、データを事前にフィルター処理し、実際にIQueryableメソッドのみを使用して、クライアントが結果をカスタマイズできるようにします。
IQueryableをリポジトリに公開することは、開発の初期段階では完全に許容できると思います。 IQueryableを直接操作して、並べ替え、フィルタリング、グループ化などを処理できるUIツールがあります。
そうは言っても、リポジトリにクラッドを公開してそれを1日と呼ぶだけでは無責任だと思います。一般的なクエリの便利な操作とクエリヘルパーを使用すると、クエリのロジックを一元化すると同時に、リポジトリのコンシューマーに小さなインターフェイスサーフェスを公開できます。
プロジェクトの最初の数回の反復では、IQueryableをリポジトリで公開することで、より迅速に成長できます。最初の完全リリースの前に、クエリ操作をプライベートまたは保護し、ワイルドクエリを1つの屋根の下に置くことをお勧めします。
私が見てきたこと(そして私が自分で実装していること)は次のとおりです:
_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)
クエリを柔軟に実行できるようになります。