2012-05-15 21 views
8

私たちのこの巨大なプロジェクトのために、より精巧なフィルタリングシステムを作成中です。主要な述語の1つは、文字列パラメータを介して比較を渡すことができることです。 "> 50" または "5-10" または "< 123.2"ラムダ式を比較して返す拡張メソッド

私は(説明するための例のように)持って何

のViewModel:

TotalCost (string) (value: "<50") 
Required (string) (value: "5-10") 

EFこれは、次の形式で表現しますモデル:私が使用したい

TotalCost (double) 
Required(double) 

式:

model => model.Where(field => field.TotalCost.Compare(viewModel.TotalCost) && field.Required.Compare(viewModel.Required)); 
私は受け取りたい

式:

model => model.Where(field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10); 

か、その

に似て何かしかし...私はどこから始めれば見当がつかない。私はそれを絞り込んだ

public static Expression Compare<T>(this Expression<Func<T, bool>> value, string compare) 

これは正しくないかもしれませんが、これは私が持っているすべてです。比較ビルダーは問題ではない、それは簡単なビットです。難しい部分は実際に式を返しています。私は、式を関数値として返すことを試みたことはありません。ですから、基本的に私が保つ必要があるのは、フィールドであり、比較式を返します。

助けが必要ですか? :X

アップデート:この可哀想

は私の問題を解決していません。それは、私が過去23時間にわたって起きてきたことが原因かもしれませんが、私はそれを拡張方法にする方法には少しも手がかりがありません。それは、「欠けている

public static Expression<Func<decimal, bool>> Compare(string arg) 
{ 
    if (arg.Contains("<")) 
     return d => d < int.Parse(arg); 

    return d => d > int.Parse(arg); 
} 

私はその関数(おそらく完全に間違っを)出し形

var ex = new ExTest(); 
var items = ex.Repo.Items.Where(x => x.Cost.Compare("<50")); 

方法がある:私が言ったように、私が好きなものを...記述する方法は基本的にあり最初の場所で比較するための「これは何か」という価値がありますが、私は式の入力を得る方法をまだ理解していません... ReSharperについては、代わりにブール値に変換することを提案します...

私の頭は今では毛羽立ちます...

アップデート2:

私は、コンソールアプリケーションのメモリ・リポジトリで動作するコードの部分を持ってする方法を見つけ出すことができました。私はまだエンティティフレームワークでそれを試しています。

public static bool Compare(this double val, string arg) 
    { 
     var arg2 = arg.Replace("<", "").Replace(">", ""); 
     if (arg.Contains("<")) 
      return val < double.Parse(arg2); 

     return val > double.Parse(arg2); 
    } 

しかし、私は非常にそれは私が更新3

後だ何を疑う:

右は、最後の答えの前に、再び座っやラムダ式を見た後、私が来ました以下のようなものでは、それは "Compare()"の正確な要件を満たしていませんが、それは 'オーバーロード - ish'ですメソッド:

public static IQueryable<T> WhereExpression<T>(this IQueryable<T> queryable, Expression<Func<T, double>> predicate, string arg) 
    { 
     var lambda = 
      Expression.Lambda<Func<T, bool>>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString())))); 

     return queryable.Where(lambda); 
    } 

はしかし、私の目にもかかわらず、すべてが論理見せかけ、私はの実行時例外を取得:

System.ArgumentException was unhandled 
    Message=Incorrect number of parameters supplied for lambda declaration 
    Source=System.Core 
    StackTrace: 
     at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters) 
     at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters) 
     at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters) 
     at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters) 

これは明らかに犯人ラインであること:

var lambda = 
       Expression.Lambda<Func<T, bool>>(Expression.LessThan(predicate.Body, Expression.Constant(double.Parse(50.ToString())))); 

私は解決策に非常に近いです。私の背中からそのエラーを取り除くことができれば、私はEFがそれをSQLに変換できるはずだと信じています。さもなければ...まあ、最後の応答はおそらく行くでしょう。

+0

私が思うに提供するように式を作成します。 試しましたか? –

+0

うん、ちょうどやった。私はtbhと思ったでしょう。 – NeroS

答えて

6

式を生成するには、それはSQL(eSQL)に変換され、Expressionを手動で生成する必要があります。ここでは、GreaterThanフィルタの作成例を示します。他のフィルタも同様の手法で作成できます。

static Expression<Func<T, bool>> CreateGreaterThanExpression<T>(Expression<Func<T, decimal>> fieldExtractor, decimal value) 
{ 
    var xPar = Expression.Parameter(typeof(T), "x"); 
    var x = new ParameterRebinder(xPar); 
    var getter = (MemberExpression)x.Visit(fieldExtractor.Body); 
    var resultBody = Expression.GreaterThan(getter, Expression.Constant(value, typeof(decimal))); 
    return Expression.Lambda<Func<T, bool>>(resultBody, xPar); 
} 

private sealed class ParameterRebinder : ExpressionVisitor 
{ 
    private readonly ParameterExpression _parameter; 

    public ParameterRebinder(ParameterExpression parameter) 
    { this._parameter = parameter; } 

    protected override Expression VisitParameter(ParameterExpression p) 
    { return base.VisitParameter(this._parameter); } 
} 

ここでは使用例を示します。すでにCreateLessThanExpressionCreateBetweenExpression機能を作っていたと( :複雑な式のあなたの創造あなたは、このようなコードを使用することについては :

static void Main(string[] args) 
{ 
    using (var ents = new StackEntities()) 
    { 
     var filter = CreateGreaterThanExpression<TestEnitity>(x => x.SortProperty, 3); 
     var items = ents.TestEnitities.Where(filter).ToArray(); 
    } 
} 

更新(我々はTestEntityエンティティのエンティティセットのTestEnititiesとStackEntites EFコンテキストを持っていることを、想定) )

static Expression<Func<T, bool>> CreateFilterFromString<T>(Expression<Func<T, decimal>> fieldExtractor, string text) 
{ 
    var greaterOrLessRegex = new Regex(@"^\s*(?<sign>\>|\<)\s*(?<number>\d+(\.\d+){0,1})\s*$"); 
    var match = greaterOrLessRegex.Match(text); 
    if (match.Success) 
    { 
     var number = decimal.Parse(match.Result("${number}")); 
     var sign = match.Result("${sign}"); 
     switch (sign) 
     { 
      case ">": 
       return CreateGreaterThanExpression(fieldExtractor, number); 
      case "<": 
       return CreateLessThanExpression(fieldExtractor, number); 
      default: 
       throw new Exception("Bad Sign!"); 
     } 
    } 

    var betweenRegex = new Regex(@"^\s*(?<number1>\d+(\.\d+){0,1})\s*-\s*(?<number2>\d+(\.\d+){0,1})\s*$"); 
    match = betweenRegex.Match(text); 
    if (match.Success) 
    { 
     var number1 = decimal.Parse(match.Result("${number1}")); 
     var number2 = decimal.Parse(match.Result("${number2}")); 
     return CreateBetweenExpression(fieldExtractor, number1, number2); 
    } 
    throw new Exception("Bad filter Format!"); 
} 
+0

これはもっと便利です。私は元の質問をもう一度更新しました。私の独自の部分的な解決策は...まあ、まだ機能していません。 – NeroS

+0

私はフィルタリングメソッドに関数を実装しました。しかし、EFにはいくつかの問題があります。まず、 "getter"がその前に(MemberExpression)をキャストしてはいけません。ただの例外です。第2に、数字の正規表現(修正するのが最も簡単なもの)は少しオフになっています(? \ d +(\。\ d *)?)、あなたの投稿にあるものは、 。 "ここに。 10進以外の値では解決されません。しかしそれ以外(y) – NeroS

+0

Regexについて:そうです。 * {0,1} *量子付きの回答が更新されました(*?*と同じです)。 –

4

C#コンパイラの最初の一見の魅力的な機能の1つが、あなたのために頑張ってくれます。あなたはおそらく、あなたがこれを行うことができます知っている:

Func<decimal, bool> totalCostIsUnder50 = d => d < 50m; 

Funcを割り当てるためにラムダ式を使用し、です。しかし、あなたははこれを行うことができます知っていました:

ある
Expression<Func<decimal, bool>> totalCostIsUnder50Expression = d => d < 50m; 

は、Func表現Expressionを割り当てるには、ラムダ式を使用できますか?それはかなりきれいです。あなたは

比較ビルダーは、それが簡単に少しだ、問題ではありません。と言う考える

の部分が実際に式を返しています

ここでは空白を記入できると仮定しています。私たちが渡したとし `」< 50" に:

Expression<Func<decimal, bool>> TotalCostCheckerBuilder(string criterion) 
{ 
    // Split criterion into operator and value 

    // when operator is < do this: 
    return d => d < value; 

    // when operator is > do this: 
    return d => d > value; 

    // and so on 
} 

最後に、あなたのExpression&&と一緒だ(まだExpression持っている)を構成するには、次の操作を行います。

var andExpression = Expression.And(firstExpression, secondExpression); 
+0

状況に該当するかどうかは不明です。コメント付きオリジナル投稿が更新されました。 – NeroS

0

難しい部分は、実際に式を返しています。

プロパティ、演算子、およびフィルタを定義する列挙型とクラスのような、より構造化された構造の中に文字列を翻訳:

Enum Parameter 
    TotalCost 
    Required 
End Enum 

Enum Comparator 
    Less 
    More 
    Equals 
End Enum 

Class Criterion 
    Public ReadOnly Parameter As Parameter 
    Public ReadOnly Comparator As Comparator 
    Public ReadOnly Value As Double 

    Public Sub New(Parameter As Parameter, Comparator As Comparator, Value As Double) 
     Me.Parameter = Parameter 
     Me.Comparator = Comparator 
     Me.Value = Value 
    End Sub 
End Class 

その後、式を作成するための関数が定義されています。撮影した

Function CreateExpression(Criteria As IEnumerable(Of Criterion)) As Expression(Of Func(Of Field, Boolean)) 
    Dim FullExpression = PredicateBuilder.True(Of Field)() 

    For Each Criterion In Criteria 
     Dim Value = Criterion.Value 

     Dim TotalCostExpressions As New Dictionary(Of Comparator, Expression(Of Func(Of Field, Boolean))) From { 
      {Comparator.Less, Function(Field) Field.TotalCost < Value}, 
      {Comparator.More, Function(Field) Field.TotalCost > Value}, 
      {Comparator.Equals, Function(Field) Field.TotalCost = Value} 
     } 

     Dim RequiredExpressions As New Dictionary(Of Comparator, Expression(Of Func(Of Field, Boolean))) From { 
      {Comparator.Less, Function(Field) Field.Required < Value}, 
      {Comparator.More, Function(Field) Field.Required > Value}, 
      {Comparator.Equals, Function(Field) Field.Required = Value} 
     } 

     Dim Expressions As New Dictionary(Of Parameter, IDictionary(Of Comparator, Expression(Of Func(Of Field, Boolean)))) From { 
      {Parameter.TotalCost, TotalCostExpressions}, 
      {Parameter.Required, RequiredExpressions}} 

     Dim Expression = Expressions(Criterion.Parameter)(Criterion.Comparator) 

     FullExpression = Expression.And(Expression) 
    Next 

    Return FullExpression 
End Function 

PredicateBuilderhere 2つの式をAND演算子で結合する必要があります。

使用法:

Function Usage() As Integer 

    Dim Criteria = { 
     New Criterion(Parameter.TotalCost, Comparator.Less, 50), 
     New Criterion(Parameter.Required, Comparator.More, 5), 
     New Criterion(Parameter.Required, Comparator.Less, 10)} 

    Dim Expression = CreateExpression(Criteria) 
End Function 

それはあなたのアップデート2の部分は、SQL Server(EF)に対して実行しないことを、正確に例

field => field.TotalCost < 50 && field.Required > 5 && field.Required < 10 
関連する問題