9

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

私が持っているもの(説明する例として)

ビューモデル:

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)

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

何か助けはありますか?:バツ

アップデート:

悲しいかな、これは私の問題を解決しません。過去23時間起きていたせいかもしれませんが、拡張メソッドにする方法については少しも手がかりがありません. 私が言ったように、私が欲しいのは...基本的に書く方法です:

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

私がその関数を形作った方法(おそらく完全に間違っている)は

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

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

そもそも比較する「this -something- value」がなく、式の入力を取得できるようにする方法をまだ理解できていません... ReSharperに関しては、変換することを提案しています代わりにブール値に...

今のところ、頭の中はフワフワです...

更新 2:

コンソール アプリケーションのメモリ リポジトリで動作するコードを作成する方法を見つけました。私はまだEntity Frameworkで試していません。

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()」の正確な要件を満たしていませんが、「オーバーロードっぽい」ですどこでメソッド:

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 に変換できるはずです。そうでなければ... まあ、最後の応答はおそらく行くでしょう。

4

3 に答える 3

7

Expression式を生成するには、手動で生成する必要がある SQL (eSQL) に変換されます。以下は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); }
}

使用例はこちら。(StackEntitesエンティティのエンティティ セット TestEnitities を持つ EF コンテキストがあると仮定しますTestEntity)

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();
    }
}

更新: 複雑な式を作成するには、次のようなコードを使用できます: (関数が既に作成されていると仮定CreateLessThanExpressionCreateBetweenExpressionます)

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!");
}
于 2012-05-15T18:05:36.400 に答える
5

C# コンパイラの一見すると魔法のような機能の 1 つが、面倒な作業を代行してくれます。あなたはおそらくこれを行うことができることを知っています:

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

つまり、ラムダ式を使用してFunc. しかし、これもできることをご存知でしたか:

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

つまり、ラムダ式を使用して、 ?を表すan を割り当てます。ExpressionFuncそれはかなりきれいです。

あなたが言うと

比較ビルダーは問題ではありません。それは簡単なことです。難しい部分は実際に式を返すことです

ここで空欄を埋めることができると思います。`"<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);
于 2012-05-15T12:06:52.130 に答える