如何计算 LINQ 表达式树中的独立布尔表达式

发布于 2024-08-04 16:02:03 字数 686 浏览 3 评论 0原文

我使用标准访问者模式来迭代 LINQ 表达式树,以生成动态 SQL WHERE 子句。

我的问题是,与 C# 不同,您不能在 SQL 中使用独立的布尔表达式;你必须将它与 1 或 0 进行比较。

给定这个假设的 lambda 表达式:

h => h.Enabled || h.Enabled == false

很容易错误地生成此代码:

WHERE Enabled OR Enabled = 0

或此代码:

WHERE (Enabled = 1) OR (Enabled = 1) = 0

当然,两者都会生成 SQL 错误。当我深入研究子树以找出可能的情况时,我应该应用什么逻辑来解决这个问题,而不让我的代码开始看起来非常迟钝?

编辑:上面的示例当然是多余的 - 我只是用它来说明一点。

可以创建此场景的示例:

h => h.Enabled
h => h.Enabled == enabled
h => h.Enabled == true

当然,最后一个示例的风格很差,但我的代码被设计为可以工作独立于程序员的技能水平,因此不满足冗余场景对我来说是一种糟糕的形式。

I'm using the standard visitor pattern to iterate through a LINQ expression tree in order to generate dynamic SQL WHERE clauses.

My issue is that unlike C#, you can't use a standalone boolean expression in SQL; you have to compare it to either 1 or 0.

Given this hypothetical lambda expression:

h => h.Enabled || h.Enabled == false

It would be easy to mistakenly generate this code:

WHERE Enabled OR Enabled = 0

or this code:

WHERE (Enabled = 1) OR (Enabled = 1) = 0

Both of course will generate an SQL error. What logic should I apply to get around this without my code starting to look really obtuse as I delve deep into subtrees to figure out what the case may be?

EDIT: The example above is of course redundant - I am only using it to illustrate a point.

Examples that could create this scenario:

h => h.Enabled
h => h.Enabled == enabled
h => h.Enabled == true

Naturally that last example is poor style, but my code is being designed to work independent of the programmer's skill level, so to not cater for redundant scenarios would be poor form on my part.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

故事还在继续 2024-08-11 16:02:03

以下情况非常简单:

h => h.Enabled == enabled
h => h.Enabled == true

这些是 BinaryExpression 节点,您可以直接将它们转换为:

WHERE (Enabled = @p0)
WHERE (Enabled = 1)

您需要处理的特殊情况是:

h => h.Enabled
h => !h.Enabled

这些在表达式树中以不同的方式表示(作为 MemberExpression)。因此,您需要对 MemberExpression 进行特殊处理,并确定它是否正在访问布尔属性。如果是,则将其转换为规范形式(检测第二个示例中的 UnaryExpression ):

WHERE (Enabled = 1)
WHERE (Enabled = 0)

或者,您可以预处理表达式树并将任何特殊情况转换为其规范形式(表达式树)形式。例如,任何符合条件的 MemberExpression 节点都可以转换为正确的 BinaryExpression

The following cases are pretty straight forward:

h => h.Enabled == enabled
h => h.Enabled == true

These are BinaryExpression nodes, and you can directly translate them into:

WHERE (Enabled = @p0)
WHERE (Enabled = 1)

The special case(s) that you need to handle are:

h => h.Enabled
h => !h.Enabled

Those are represented differently in the expression tree (as a MemberExpression). So you would need to special case the MemberExpression and determine if it's accessing a boolean property or not. If it is, then you translate it into the canonical form (detecting the UnaryExpression in the second example):

WHERE (Enabled = 1)
WHERE (Enabled = 0)

Alternatively, you might be able to pre-process the expression tree and translate any special cases into their canonical (expression tree) form. For example, any MemberExpression nodes that fit the criteria could be transformed into the correct BinaryExpression.

划一舟意中人 2024-08-11 16:02:03

在评估运算符之前是否无法完全处理操作数?

IE。 Eval every:

h => h.Enabled
h => h.Enabled == enabled
h => h.Enabled == true

to ,

WHERE (Enabled = 1)

然后在 lambda 中包含运算符的情况下,使用等效的 SQL 处理呈现的操作数集合以满足运算符要求。

Is it not possible to process the operands completely before eveluating the operators?

Ie. Eval each of:

h => h.Enabled
h => h.Enabled == enabled
h => h.Enabled == true

to

WHERE (Enabled = 1)

and then in the case where operators are included in the lambda, process the collection of rendered operands with the equivalent SQL to meet the operator requirements.

眼前雾蒙蒙 2024-08-11 16:02:03

其他人可能有同样的问题。我通过将最后一个 BinaryExpression 保存到变量中解决了这个问题
这将解决以下不同的布尔子句

Where(f => f.IsActive == true || !f.IsActive == false || (f.Name == "adfd") && (f.IsActive || !f.IsBin ) )

This处理独立布尔值的代码

private string GetFilterMember(MemberExpression exp)
{
    var str = exp.ToString();
    if (str.Count(f => f == '.') > 1)
    {
        var expType = exp.Type;
        var declareType = exp.Member.DeclaringType;
        if (declareType == typeof(string))
        {
            if (exp.Member.Name == StringMethods.Length)
            {
                return $"{StringMethods.GetODataMethod(StringMethods.Length)}({exp.NameLeftPart()})";
            }
        }
        else if (declareType == typeof(DateTime))
        {

        }
    }
    if (exp.Type == typeof(bool))
    {
        if (prviousBinaryExp != null && (prviousBinaryExp.Left == exp || prviousBinaryExp.Right == exp || (prviousBinaryExp.Left.NodeType == ExpressionType.Not && ((UnaryExpression)prviousBinaryExp.Left).Operand == exp) || (prviousBinaryExp.Right.NodeType == ExpressionType.Not && ((UnaryExpression)prviousBinaryExp.Right).Operand == exp)) && (prviousBinaryExp.NodeType != ExpressionType.Equal && prviousBinaryExp.NodeType != ExpressionType.NotEqual))
        {          
            return $"{exp.Member.Name}{ODataQueryOperator.Equal}{FilterBuilder(Expression.Constant(true))}";
        }
    }
    return exp.Member.Name;
}
BinaryExpression prviousBinaryExp = null;
protected virtual string FilterBuilder(Expression exp)
{

    switch (exp.NodeType)
    {
        case ExpressionType.OrElse:
        case ExpressionType.AndAlso:
        case ExpressionType.Equal:
            prviousBinaryExp = (BinaryExpression)exp;
            return BinaryExpressionBuilder(((BinaryExpression)exp).Left, ODataQueryOperator.GetLogicalOperator(exp.NodeType), ((BinaryExpression)exp).Right);
        case ExpressionType.Constant:
            return GetConstant(exp.Type, ((ConstantExpression)exp).Value);
        case ExpressionType.MemberAccess:
            return GetFilterMember((MemberExpression)exp);
        case ExpressionType.Not:
            return UnaryExpressionBuilder((UnaryExpression)exp);
        case ExpressionType.Call:
            return MethodBuilder((MethodCallExpression)exp);
        default:
            return string.Empty;

    }
}

不在此变量下 BinaryExpression prviousBinaryExp = null;
当您点击 BinaryExpression 时,然后将该表达式保存在上面的变量中

case ExpressionType.OrElse:
case ExpressionType.AndAlso:
case ExpressionType.Equal:
     prviousBinaryExp = (BinaryExpression)exp;
     return BinaryExpressionBuilder(((BinaryExpression)exp).Left, ODataQueryOperator.GetLogicalOperator(exp.NodeType), ((BinaryExpression)exp).Right);

这是检查先前表达式是否为二进制表达式以及当前表达式是否为 BinaryExpression 的一部分的逻辑 LeftRight 表达式。我们将检查 Nodtype,它不应该是 ExpressionType.EqualExpressionType.NotEqual(f.IsActive == true)。我们还将确保如果 BinaryExpressionLeftRightUnaryExpression,那么我们将检查它的 UnaryExpression.Operand 等于当前表达式。这将处理 !f.Enabled 或 !f.IsActive 等。

最后,我将表达式转换为 OData 等效字符串。这是

        // converted String IsActive to OData equivalent
        //         IsActive          eq            true
        return $"{exp.Member.Name}{ODataQueryOperator.Equal}{FilterBuilder(Expression.Constant(true))}";

Other may have same question. I have solved this by saving last BinaryExpression into a variable
This will solve the following different boolean clauses

Where(f => f.IsActive == true || !f.IsActive == false || (f.Name == "adfd") && (f.IsActive || !f.IsBin) )

This the code which handles standalone boolean

private string GetFilterMember(MemberExpression exp)
{
    var str = exp.ToString();
    if (str.Count(f => f == '.') > 1)
    {
        var expType = exp.Type;
        var declareType = exp.Member.DeclaringType;
        if (declareType == typeof(string))
        {
            if (exp.Member.Name == StringMethods.Length)
            {
                return 
quot;{StringMethods.GetODataMethod(StringMethods.Length)}({exp.NameLeftPart()})";
            }
        }
        else if (declareType == typeof(DateTime))
        {

        }
    }
    if (exp.Type == typeof(bool))
    {
        if (prviousBinaryExp != null && (prviousBinaryExp.Left == exp || prviousBinaryExp.Right == exp || (prviousBinaryExp.Left.NodeType == ExpressionType.Not && ((UnaryExpression)prviousBinaryExp.Left).Operand == exp) || (prviousBinaryExp.Right.NodeType == ExpressionType.Not && ((UnaryExpression)prviousBinaryExp.Right).Operand == exp)) && (prviousBinaryExp.NodeType != ExpressionType.Equal && prviousBinaryExp.NodeType != ExpressionType.NotEqual))
        {          
            return 
quot;{exp.Member.Name}{ODataQueryOperator.Equal}{FilterBuilder(Expression.Constant(true))}";
        }
    }
    return exp.Member.Name;
}
BinaryExpression prviousBinaryExp = null;
protected virtual string FilterBuilder(Expression exp)
{

    switch (exp.NodeType)
    {
        case ExpressionType.OrElse:
        case ExpressionType.AndAlso:
        case ExpressionType.Equal:
            prviousBinaryExp = (BinaryExpression)exp;
            return BinaryExpressionBuilder(((BinaryExpression)exp).Left, ODataQueryOperator.GetLogicalOperator(exp.NodeType), ((BinaryExpression)exp).Right);
        case ExpressionType.Constant:
            return GetConstant(exp.Type, ((ConstantExpression)exp).Value);
        case ExpressionType.MemberAccess:
            return GetFilterMember((MemberExpression)exp);
        case ExpressionType.Not:
            return UnaryExpressionBuilder((UnaryExpression)exp);
        case ExpressionType.Call:
            return MethodBuilder((MethodCallExpression)exp);
        default:
            return string.Empty;

    }
}

Not down this variable BinaryExpression prviousBinaryExp = null;
When you hit BinaryExpression then save that expression in above variable

case ExpressionType.OrElse:
case ExpressionType.AndAlso:
case ExpressionType.Equal:
     prviousBinaryExp = (BinaryExpression)exp;
     return BinaryExpressionBuilder(((BinaryExpression)exp).Left, ODataQueryOperator.GetLogicalOperator(exp.NodeType), ((BinaryExpression)exp).Right);

This is the logic which checks if a previous expression was binaryexpression and the the current expression is the part of the BinaryExpression's Left or Right expression. We will check the Nodtype which should not be ExpressionType.Equal and ExpressionType.NotEqual(f.IsActive == true). We will also make sure that if Left and Right of BinaryExpression is UnaryExpression than we will check it's UnaryExpression.Operand is equal to current expression. This will handled !f.Enabled or !f.IsActive etc.

Finally I am converting Expression to OData equivalent string. Which is

        // converted String IsActive to OData equivalent
        //         IsActive          eq            true
        return 
quot;{exp.Member.Name}{ODataQueryOperator.Equal}{FilterBuilder(Expression.Constant(true))}";
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文