0

MSDNのドキュメントには、式ツリーを解析する良い例があります

// Create an expression tree.
Expression<Func<int, bool>> exprTree = num => num < 5;

// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;

Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
              param.Name, left.Name, operation.NodeType, right.Value);

しかし、私はこのようなものを解析する方法の例を見ていません:

MyDomainObject foo;
Expression<Func<bool>> exprTree = () => ((foo.Scale < 5 || foo.Scale > 20) && foo.Scale <> -100) || foo.IsExempt;

私の目標は、(1)任意のレベルの括弧で囲まれたネストを処理し、(2)式ツリーの解析の結果として同等のSQL「where」句を含む文字列を生成できるユーティリティを見つけるか構築することです。これに対処するnugetパッケージを支援または知っている可能性のあるコードスニペットを持っている人はいますか?

上記のネストされた式の場合、「MyDomainObject」というDBテーブルを想定すると、正しいSQLwhere句の文字列出力は次のようになります。

(( Scale < 5 or Scale > 20) and Scale != -100) or IsExempt = true

明らかに、私の想像上のパーサーは、二項演算子がない場合、「IsExempt = true」の場合のように、単に「true」をアサートすることを前提としています。

4

2 に答える 2

2

これは、少なくとも入力を有効なSQL式に変換する実装です。より多くの式タイプを自分で実装する必要がありますが、それがどのように機能するかを理解できます。

この答えはたまたま風塚の答えと非常に似ていますが、式ツリーにExpression.NodeType演算子がないため、演算子を見つけるために使用されます。MethodInfos

また、これにより実際に必要な数よりも多くの括弧が生成されることに注意してください。括弧の数を減らすには、SQLでの演算子の優先順位を考慮して、式をさらに分析する必要があります。

public static string GetSqlExpression(Expression expression)
{
    if (expression is BinaryExpression)
    {
        return string.Format("({0} {1} {2})",
            GetSqlExpression(((BinaryExpression)expression).Left),
            GetBinaryOperator((BinaryExpression)expression),
            GetSqlExpression(((BinaryExpression)expression).Right));
    }

    if (expression is MemberExpression)
    {
        MemberExpression member = (MemberExpression)expression;

        // it is somewhat naive to make a bool member into "Member = TRUE"
        // since the expression "Member == true" will turn into "(Member = TRUE) = TRUE"
        if (member.Type == typeof(bool))
        {
            return string.Format("([{0}] = TRUE)", member.Member.Name);
        }

        return string.Format("[{0}]", member.Member.Name);
    }

    if (expression is ConstantExpression)
    {
        ConstantExpression constant = (ConstantExpression)expression;

        // create a proper SQL representation for each type
        if (constant.Type == typeof(int) ||
            constant.Type == typeof(string))
        {
            return constant.Value.ToString();
        }

        if (constant.Type == typeof(bool))
        {
            return (bool)constant.Value ? "TRUE" : "FALSE";
        }

        throw new ArgumentException();
    }

    throw new ArgumentException();
}

public static string GetBinaryOperator(BinaryExpression expression)
{
    switch (expression.NodeType)
    {
        case ExpressionType.Equal:
            return "=";
        case ExpressionType.NotEqual:
            return "<>";
        case ExpressionType.OrElse:
            return "OR";
        case ExpressionType.AndAlso:
            return "AND";
        case ExpressionType.LessThan:
            return "<";
        case ExpressionType.GreaterThan:
            return ">";
        default:
            throw new ArgumentException();
    }
}

結果は次のとおりです。

(((([Scale] < 5) OR ([Scale] > 20)) AND ([Scale] <> -100)) OR ([IsExempt] = TRUE))

次のようなメソッドを呼び出します。

string sqlExpression = GetSqlExpression(exprTree.Body);

より機能的な方法で式ツリーを構築することをお勧めします。Func<bool>コンクリートを使用して構築する代わりに、を使用fooする必要がありますFunc<Foo, bool>。ただし、とにかく動作します。正しく見えません。

Expression<Func<Foo, bool>> exprTree =
    (foo) => ((foo.Scale < 5 || foo.Scale > 20) && foo.Scale != -100) || foo.IsExempt == true;

明らかに、LINQ to Entitiesを使用できる場合は、通常、SQLテキストを自分で作成する必要はありません。LINQtoエンティティと式ツリーの両方に.NET3.5が必要であり、実際にLINQtosqlステートメントを変換できます。

のような式がSQLServerで機能するかどうかはわかりませんIsExempt = TRUEIsExempt = 1データ型がであるため、そうあるべきだと思いますbit。また、SQL式ではまたはを使用できないため、またはのような式は個別に処理する必要がありValue == nullます。またはでなければなりません。Value != nullValue = NULLValue <> NULLValue IS NULLValue IS NOT NULL

于 2013-03-02T20:55:30.887 に答える
1

したがって、問題はそれ自体では「解析」されていないように思われます(式はすでにC#式として解析されているため)。必要なのは、式ツリーをトラバースしてSQL式を出力することです。

回避できるのであれば、独自のコードをロールすることはお勧めしません。ほとんどの人にとって、LINQ to Entitiesは、SQLを完全に非表示にしながら、基本的にこれを実行するため、おそらくより良い方法です。

他の要件(.NETのバージョンが低い、またはSQL文字列が必要)があり、自分でコードを記述したい場合は、再帰関数を使用してこれを行うことができます。この関数は式を受け取り、SQL句を文字列として返すことができます。

次の行に沿った何か(これはテストされていません。疑似コードとして扱います):

public string WriteClause(Expression exp)
{
    if (exp is ParameterExpression)
    {
        return (exp as ParameterExpression).Name;
    }
    else if (exp is BinaryExpression)
    {
        var binaryExpression = exp as BinaryExpression;

        return "(" +
               WriteClause(binaryExpression.Left) + " "
               GetSqlOperator(binaryExpression.Method) + " "
               WriteClause(binaryExpression.Right) +
               ")";
    }
    else if...

    ...etc...

}

public string GetSqlOperator(MethodInfo method)
{
    switch (method.Name)
    {
        case "Add":
            return "+";
        case "Or":
            return "or";

        ...etc...
    }
}

再帰的であるということは、このアプローチがあらゆるレベルの括弧の深さを処理する必要があることを意味します。少しナイーブなので、必要以上に括弧を入れます。

于 2013-03-02T19:28:56.623 に答える