2015-09-24 6 views
7

私はEFを使用しています。レコードにはさまざまな操作が実行されるため、日付のフィールドが数多くあるデータベーステーブルがあります。 私は現在、これらの日付によるフィルタリングを含む報告システムを構築していますが、フィルタ(範囲内のこの日付など)は各フィールドで同じ動作をするため、フィルタリングロジックを再利用したいので単一の日付フィルタを作成し、それを各フィールドで使用するだけです。LINQでエンティティにフィールドフィルタを再利用する方法

私の最初のフィルタリングコードは次のようになります。

DateTime? dateOneIsBefore = null; 
DateTime? dateOneIsAfter = null; 

DateTime? dateTwoIsBefore = null; 
DateTime? dateTwoIsAfter = null; 

using (var context = new ReusableDataEntities()) 
{ 
    IEnumerable<TestItemTable> result = context.TestItemTables 
     .Where(record => ((!dateOneIsAfter.HasValue 
       || record.DateFieldOne > dateOneIsAfter.Value) 
      && (!dateOneIsBefore.HasValue 
       || record.DateFieldOne < dateOneIsBefore.Value))) 
     .Where(record => ((!dateTwoIsAfter.HasValue 
       || record.DateFieldTwo > dateTwoIsAfter.Value) 
      && (!dateTwoIsBefore.HasValue 
       || record.DateFieldTwo < dateTwoIsBefore.Value))) 
     .ToList(); 

    return result; 
} 

これは正常に動作しますが、私はフィルタアルゴリズムとして「どこ方法で重複コードを削減することを好む各日付フィールドでも同じです。私が好む何

は、私は多分拡張メソッドを使用して一致アルゴリズムをカプセル化することができ、次のようなもの(私は後でフィルタ値のクラスまたは構造体を作成します)です。

DateTime? dateOneIsBefore = null; 
DateTime? dateOneIsAfter = null; 

DateTime? dateTwoIsBefore = null; 
DateTime? dateTwoIsAfter = null; 

using (var context = new ReusableDataEntities()) 
{ 
    IEnumerable<TestItemTable> result = context.TestItemTables 
     .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
     .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
     .ToList(); 

    return result; 
} 

私のフィルタリングコードがある場合は、次のように、上記のコードを使用して

internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Func<TestItemTable, DateTime> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) 
{ 
    source = source.Where(record => ((!dateIsAfter.HasValue 
      || fieldData.Invoke(record) > dateIsAfter.Value) 
     && (!dateIsBefore.HasValue 
      || fieldData.Invoke(record) < dateIsBefore.Value))); 

    return source; 
} 

IEnumerable<TestItemTable> result = context.TestItemTables 
    .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
    .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
    .ToList(); 
01を拡張メソッドは次のように見ることができます

A first chance exception of type 'System.NotSupportedException' occurred in EntityFramework.SqlServer.dll 

Additional information: LINQ to Entities does not recognize the method 'System.DateTime Invoke(RAC.Scratch.ReusableDataFilter.FrontEnd.TestItemTable)' method, and this method cannot be translated into a store expression. 

私が持っている問題は、私は私のフィルタリングコードを変更した場合ので、SQLにうまく解決しないこの技術として照会されている特定のフィールドを取得するために呼び出すの使用である:

は、私は次の例外を取得しますそれがエラーなしで実行され、次の

IEnumerable<TestItemTable> result = context.TestItemTables 
    .ToList() 
    .AsQueryable() 
    .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
    .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
    .ToList(); 

これで問題が(拡張方式によるフィルタリング前にテーブル全体にToListメソッドを使用して)コードがデータベース全体に引っ張るとオブジェクトとして代わりに照会を照会することです基礎となるデータベースであるため、スケールがないble。

また、LinqkitのPredicateBuilderを使用して調査しましたが、Invokeメソッドを使用しないでコードを書き込む方法が見つかりませんでした。

私は、フィールド名を含む文字列としてクエリの一部を表現できる手法があることを知っていますが、このコードを書くより安全な方法を使うことをお勧めします。

また、理想的な世界では、単一の「アイテム」レコードに関連する複数の「日付」レコードを持つようにデータベースを再設計できましたが、この方法でデータベーススキーマを変更する自由はありません。

拡張を記述してInvokeを使用しないようにする別の方法がありますか、フィルタリングコードを別の方法で再利用する必要がありますか?

答えて

2

はい、LinqKitはここに行く方法です。しかし、あなたはあなたの拡張メソッドにいくつかの作品が欠落している:

internal static IQueryable<TestItemTable> WhereFilter(this IQueryable<TestItemTable> source, Expression<Func<TestItemTable, DateTime>> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) 
{ 
    source = source.AsExpandable().Where(record => ((!dateIsAfter.HasValue 
      || fieldData.Invoke(record) > dateIsAfter.Value) 
      && (!dateIsBefore.HasValue 
      || fieldData.Invoke(record) < dateIsBefore.Value))); 

    return source; 
} 

私はExpression<Func<TestItemTable, DateTime>>に第二のパラメータを変更し、LinqKitのAsExpandable()方法に欠けているの呼び出しを追加しました。このようにして、Invoke()はLinqKitのInvoke()を呼び出して、その魔法を実行することができます。

使用方法:お使いの場合には

IEnumerable<TestItemTable> result = context.TestItemTables 
    .WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 
    .WhereFilter(record => record.DateFieldTwo, dateTwoIsBefore, dateTwoIsAfter) 
    .ToList(); 
3

があまり重複がないが、それでも私はあなたが(例として)生の表現であなたがやりたいことができる方法を示します:

internal static class QueryableExtensions { 
    internal static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, Expression<Func<T, DateTime>> fieldData, DateTime? dateIsBefore, DateTime? dateIsAfter) { 
     if (dateIsAfter == null && dateIsBefore == null) 
      return source; 
     // this represents you "record" parameter in lambda 
     var arg = Expression.Parameter(typeof(T), "record"); 
     // this is the name of your field ("DateFieldOne") 
     var dateFieldName = ((MemberExpression)fieldData.Body).Member.Name; 
     // this is expression "c.DateFieldOne" 
     var dateProperty = Expression.Property(arg, typeof(T), dateFieldName); 
     Expression left = null; 
     Expression right = null; 
     // this is "c.DateFieldOne > dateIsAfter" 
     if (dateIsAfter != null) 
      left = Expression.GreaterThan(dateProperty, Expression.Constant(dateIsAfter.Value, typeof(DateTime))); 
     // this is "c.DateFieldOne < dateIsBefore" 
     if (dateIsBefore != null) 
      right = Expression.LessThan(dateProperty, Expression.Constant(dateIsBefore.Value, typeof(DateTime))); 
     // now we either combine with AND or not, depending on values 
     Expression<Func<T, bool>> combined; 
     if (left != null && right != null) 
      combined = Expression.Lambda<Func<T, bool>>(Expression.And(left, right), arg); 
     else if (left != null) 
      combined = Expression.Lambda<Func<T, bool>>(left, arg); 
     else 
      combined = Expression.Lambda<Func<T, bool>>(right, arg); 
     // applying that to where and done. 
     source = source.Where(combined); 
     return source; 
    } 
} 
あなたが期待するよう

コールは次のとおりです。式の扱い

WhereFilter(record => record.DateFieldOne, dateOneIsBefore, dateOneIsAfter) 

最初は奇妙に見えますが、少なくともかもしれません単純なケースでは複雑すぎるものはありません。

+0

(私の謝罪 - 私は多分サンプルコードでこれを含まれている必要があります)null以外の日付フィルタ値で ...私も何かが足りない可能性があります、ありがとうござい例えば、: 日時を? dateOneIsAfter =新しいDateTime(2000,12,31); 私は例外を取得:入力する「System.Linq.Expressions.UnaryExpression」タイプのオブジェクトをキャストすることができません「システム:型「System.InvalidCastExceptionの」の 未処理の例外がReusableDataFilter.FrontEnd.exe で発生しました追加情報.Linq.Expressions.MemberExpression '。 行: var dateFieldName =((MemberExpression)fieldData.Body).Member.Name; – NvR

+0

@NvRは、nullの入力が不可能な日付での作業に対する最新の回答を更新しました。 – Evk

+0

OPはこのコードを例外にして例外にしているが、それは何とかコードを修正しなければならないことを意味し、 'fieldData'は' e => e.DateFieldOne'のようになり、 'Body'は必ず' MemberExpression' (しかし何とか 'UnaryExpresion'だと、実際には' e => e.DateFieldOne.Value'を使うかもしれません)。元のコード( 'DateTime? ')は' e => DateFieldOne'で動作するはずです。現在のコードは 'e => e.DateFieldOne.Value'で動作するはずです。だから私は努力のために+1します。 OPはエクスプレッションについて学ぶチャンスをバイパスするかもしれません。 – Hopeless

関連する問題