5

DALリポジトリでビルドしているときに、私はパイプとフィルターという概念を見つけました。私はそれについてhereを読んで、hereからスクリーンキャストを見ました。私はまだこのパターンを実装する方法についてはわかりません。理論的にはすべてが良いと思いますが、エンタープライズシナリオで実際にどのように実装していますか?どのようにLinqToSQL/Entity Framework/NHibernateでパイプとフィルタパターンを実装しますか?

質問に記載されているデータマッパー/ ORMとの関連で、このパターンの説明、ヒント、または例があれば、私は感謝します。

ありがとうございます!

+0

何らかの理由で質問をdownvoteする場合は、コメントを追加してください。 – Perpetualcoder

+0

質問に間違いがありますか? – Perpetualcoder

答えて

11

最終的には、LINQ on IEnumerable<T>は、パイプとフィルタの実装であるです。 IEnumerable<T>ストリーミング APIです。つまり、すべてを一度に読み込んでレコードの大きなバッファを返すのではなく、(イテレータブロックを使用して)リクエストするとデータが遅れて返されることを意味します。

これはあなたのクエリことを意味します

var qry = from row in source // IEnumerable<T> 
      where row.Foo == "abc" 
      select new {row.ID, row.Name}; 

は次のとおりです。

var qry = source.Where(row => row.Foo == "abc") 
      .Select(row = > new {row.ID, row.Name}); 

あなたがこの上で列挙として、それが遅延したデータを消費します。あなたはJon SkeetのVisual LINQでこれをグラフィカルに見ることができます。パイプを壊すのはバッファリングを強制するものだけです。 OrderBy,GroupByなど大量の作業では、Jonと私自身はPush LINQでバッファリングせずに集計を行っていました。

IQueryable<T>(ほとんどのORMツール、LINQ-to-SQL、Entity Framework、LINQ-NHibernateに公開されています)は少し違っています。データベースエンジンは大部分の作業を行うため、ほとんどのステップがすでに完了している可能性があります。残っているのはIDataReaderを消費し、これをオブジェクト/値に投影することですが、これはまだ一般的にはパイプですあなたは、企業での使用についてなど

.ToArray().ToList()を呼び出さない限り... my viewが、リポジトリ内の構成可能なクエリを記述するためにIQueryable<T>を使用する罰金であるということですが、彼らは休暇いけない(IQueryable<T>IEnumerable<T>を実装します)リポジトリ - リポジトリの内部操作を呼び出し元の対象とするため、テスト/プロファイル/最適化などを適切に行うことができません。リポジトリ内のこれまでのものは返すが、リスト/配列を返す。これは私のリポジトリが実装を知らないままでいることを意味します。

これは残念です。IQueryable<T>をリポジトリメソッドから「返す」という誘惑がかなり大きいです。例えば、これは呼び出し側がページング/フィルタ/ etcを追加することを可能にするが、実際にデータをまだ消費していないことを覚えておく。これにより、リソース管理が苦労します。また、MVCなどでは、コントローラ.ToList()などを呼び出して、データアクセスを制御しているビューでないようにする必要があります(そうしないと、コントローラを正しくユニットテストできません)。DAL内のフィルタの

安全(IMO)の使用は、のようなものになります:

public Customer[] List(string name, string countryCode) { 
    using(var ctx = new CustomerDataContext()) { 
     IQueryable<Customer> qry = ctx.Customers.Where(x=>x.IsOpen); 
     if(!string.IsNullOrEmpty(name)) { 
      qry = qry.Where(cust => cust.Name.Contains(name)); 
     } 
     if(!string.IsNullOrEmpty(countryCode)) { 
      qry = qry.Where(cust => cust.CountryCode == countryCode); 
     } 
     return qry.ToArray(); 
    } 
} 

ここでは、オンザフライでフィルターを追加しましたが、私たちはToArrayを呼び出すまで何も起こりません。この時点で、データが取得され、返されます(プロセスでデータコンテキストを廃棄します)。これは完全に単体テストできます。 (などをTSQLへSomeUnmappedFunctionを翻訳することはできません)

var custs = customerRepository.GetCustomers() 
     .Where(x=>SomeUnmappedFunction(x)); 

そして、我々のDALは、失敗を開始突然のすべて:私たちは似た何かをしたが、ちょうどIQueryable<T>返された場合、呼び出し元のような何かを行う可能性があります。あなたはまだlotのリポジトリ内の興味深いものを行うことができます。

ここでの唯一の苦労は、さまざまな呼び出しパターン(ページングの有無など)をサポートするためにいくつかのオーバーロードが必要なことです。 optional/named parametersが到着するまでは、私はインターフェイス上で拡張メソッドを使用するのが最善の答えです。その方法は、私が唯一の具体的なリポジトリの実装が必要になります。

class CustomerRepository { 
    public Customer[] List(
     string name, string countryCode, 
     int? pageSize, int? pageNumber) {...} 
} 
interface ICustomerRepository { 
    Customer[] List(
     string name, string countryCode, 
     int? pageSize, int? pageNumber); 
} 
static class CustomerRepositoryExtensions { 
    public static Customer[] List(
      this ICustomerRepository repo, 
      string name, string countryCode) { 
     return repo.List(name, countryCode, null, null); 
    } 
} 

は、今、私たちはICustomerRepository上(拡張メソッドなど)の仮想過負荷を持っている - ので、私たちの呼び出し側がページングを指定しなくてもrepo.List("abc","def")を使用することができます。


最後に、LINQを使用せずに、パイプとフィルタを使用すると、さらに苦痛になります。何らかのテキストベースのクエリ(TSQL、ESQL、HQL)を作成します。明らかに文字列を追加することはできますが、それはあまり「パイプ/フィルタ」ではありません。 "Criteria API"は少し上手ですが、LINQほどエレガントではありません。