【问题标题】:How can I create a Linq AND expression in C#? [duplicate]如何在 C# 中创建 Linq AND 表达式? [复制]
【发布时间】:2021-03-26 02:21:49
【问题描述】:

我一直在尝试在 Linq 中创建一个 AND 表达式,但没有令人满意的结果。这是我创建的方法:

private List<Item> GetItems(int projectId, bool checkFlag)
{
    Expression<Func<Item, bool>> sqlExpression = item => item.ProjectId == projectId;

    if (checkFlag)
    {
        Expression<Func<Item, bool>> newExpression = item => item.Flag;
        sqlExpression = Expression.And(sqlExpression, newExpression);
    }

    return db.Table<Items>().Where(sqlExpression).ToList();
}

这是 Item 类:

public class Item
{
    [PrimaryKey]
    public int Id { get; set; }
    public int ProjectId { get; set; }
    public bool Flag { get; set; }
}

编译时的问题出在创建AND表达式的那一行:

Expression.And(sqlExpression, newExpression);

Visual Studio 返回的错误是:

错误 CS0029:无法隐式转换类型 'System.Linq.Expressions.BinaryExpression' 到 'System.Linq.Expressions.Expression>' (CS0029)

我知道这个例子可以通过改变第一个表达式来解决,但这不是我想要做的:

Expression<Func<Item, bool>> sqlExpression = item => (item.ProjectId == projectId) && (!checkFlag || item.Flag);

【问题讨论】:

  • 您是否有理由想通过构建一个将两者结合起来的新表达式来解决这个问题?通常我只会做var result = db.Table&lt;Items&gt;().Where(i =&gt; i.ProjectId == projectId); 然后if (checkFlag) result = result.Where(i =&gt; i.Flag); 最后return result.ToList();

标签: c# linq expression


【解决方案1】:

这里有两个问题。首先是如果你使用Expression.And,这相当于二元运算符&amp;,这可能不是你想要的。你必须写

Expression.AndAlso(e1, e2)

得到e1 &amp;&amp; e2的等价物。

更大的问题是您试图将两个 lambda 表达式与 &amp;&amp; 结合起来,这是不可能的,因为 &amp;&amp; 适用于布尔表达式。你想要的是,给定两个 lambda 表达式 lambda1 = x =&gt; e1lambda2 = x =&gt; e2,返回 x =&gt; e1 &amp;&amp; e2

为了实现这一点,请按照以下步骤操作:

  1. 新建一个ParameterExpressionp
  2. lambda1.Body 中的参数替换为p(您可以使用ExpressionVisitor 执行此操作)以获得renamedBody1
  3. lambda2.Body中的参数替换为p得到renamedBody2
  4. 现在你可以构造了
Expression.Lambda<Func<Item, bool>>(
    Expression.AndAlso(renamedBody1, renamedBody2), p);

这是您可以用作Where 方法的参数的表达式。


注意:如果您确保在使用Expression.Lambda() 构造两个 lambda 表达式时使用相同的参数表达式,则可以避免参数重命名。但是,您不能像在代码中那样使用 =&gt; 语法优雅地构造它们,即使它们在两个 lambda 表达式中都被命名为 item,也会产生 不同的 参数。


完整代码

或者,只需在以下代码中使用And 扩展方法(lambda1.And(lambda2)):

    /// <summary>
    /// Extension methods related to <see cref="Expression" />.
    /// </summary>
    public static class ExpressionExtensions
    {
        /// <summary>
        /// Renames all parameter names equal to <paramref name="oldParameterName" /> in
        /// <paramref name="expression" /> to <paramref name="newParameter" />.
        /// </summary>
        [NotNull]
        public static Expression ReplaceParameter([NotNull] this Expression expression,
            [NotNull] string oldParameterName,
            [NotNull] ParameterExpression newParameter)
        {
            if (expression == null) throw new ArgumentNullException(nameof(expression));
            if (oldParameterName == null) throw new ArgumentNullException(nameof(oldParameterName));
            if (newParameter == null) throw new ArgumentNullException(nameof(newParameter));
            AlphaRenamingExpressionVisitor visitor =
                new AlphaRenamingExpressionVisitor(oldParameterName, newParameter);
            return visitor.Visit(expression).AsNotNull();
        }

        /// <summary>
        /// Returns the conjunction of the two given predicate expressions, performing alpha renamings if necessary.
        /// </summary>
        [NotNull]
        public static Expression<Func<T, bool>> And<T>([NotNull] this Expression<Func<T, bool>> e1,
            [NotNull] Expression<Func<T, bool>> e2)
        {
            if (e1 == null) throw new ArgumentNullException(nameof(e1));
            if (e2 == null) throw new ArgumentNullException(nameof(e2));
            return binaryOperation(e1, e2, Expression.AndAlso);
        }

        /// <summary>
        /// Returns the negation of the given predicate expression.
        /// </summary>
        [NotNull]
        public static Expression<Func<T, bool>> Not<T>([NotNull] this Expression<Func<T, bool>> e1)
        {
            if (e1 == null) throw new ArgumentNullException(nameof(e1));
            return Expression.Lambda<Func<T, bool>>(Expression.Not(e1.Body),
                e1.Parameters);
        }

        /// <summary>
        /// Returns the disjunction of the two given predicate expressions, performing alpha renamings if necessary.
        /// </summary>
        [NotNull]
        public static Expression<Func<T, bool>> Or<T>([NotNull] this Expression<Func<T, bool>> e1,
            [NotNull] Expression<Func<T, bool>> e2)
        {
            if (e1 == null) throw new ArgumentNullException(nameof(e1));
            if (e2 == null) throw new ArgumentNullException(nameof(e2));
            return binaryOperation(e1, e2, Expression.OrElse);
        }

        [NotNull]
        private static Expression<Func<T, bool>> binaryOperation<T>([NotNull] Expression<Func<T, bool>> e1,
            [NotNull] Expression<Func<T, bool>> e2, [NotNull] Func<Expression, Expression, Expression> binaryOp)
        {
            if (binaryOp == null) throw new ArgumentNullException(nameof(binaryOp));
            if (e1.Parameters[0].Equals(e2.Parameters[0]))
            {
                return Expression.Lambda<Func<T, bool>>(binaryOp(e1.Body, e2.Body), e1.Parameters[0]);
            }

            ParameterExpression newParam = Expression.Parameter(typeof(T), "x" + Guid.NewGuid().ToString("N"));

            Expression renamedBody1 = e1.Body.ReplaceParameter(e1.Parameters[0].Name, newParam);
            Expression renamedBody2 = e2.Body.ReplaceParameter(e2.Parameters[0].Name, newParam);
            return Expression.Lambda<Func<T, bool>>(binaryOp(renamedBody1, renamedBody2),
                newParam);
        }

        private class AlphaRenamingExpressionVisitor : ExpressionVisitor
        {
            [NotNull] private readonly string oldParameterName;
            [NotNull] private readonly ParameterExpression newParameter;

            /// <summary>
            /// Initializes a new instance of <see cref="AlphaRenamingExpressionVisitor" /> with the given parameters.
            /// </summary>
            public AlphaRenamingExpressionVisitor([NotNull] string oldParameterName,
                [NotNull] ParameterExpression newParameter)
            {
                this.oldParameterName = oldParameterName ?? throw new ArgumentNullException(nameof(oldParameterName));
                this.newParameter = newParameter ?? throw new ArgumentNullException(nameof(newParameter));
            }

            /// <inheritdoc />
            [NotNull]
            protected override Expression VisitParameter([NotNull] ParameterExpression node)
            {
                if (node == null)
                {
                    throw new ArgumentNullException(nameof(node));
                }

                return node.Name == oldParameterName ? newParameter : node;
            }
        }
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多