0

I have been trying to create an AND expression in Linq with no satisfactory results. This is the method I have created:

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

And this is the Item class:

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

The problem at compile time is located at the line where the AND expression is created:

Expression.And(sqlExpression, newExpression);

The error that Visual Studio returns is:

Error CS0029: Cannot implicitly convert type 'System.Linq.Expressions.BinaryExpression' to 'System.Linq.Expressions.Expression<System.Func<Item, bool>>' (CS0029)

I know this example could be fixed just by changing the first expression, but it is not what I intended to do:

Expression<Func<Item, bool>> sqlExpression = item => (item.ProjectId == projectId) && (!checkFlag || item.Flag);
Fer
  • 1,956
  • 2
  • 28
  • 35
  • Is there a reason you want to solve this by constructing a new expression that combines them both? Normally I would just do `var result = db.Table().Where(i => i.ProjectId == projectId);` then `if (checkFlag) result = result.Where(i => i.Flag);` finally `return result.ToList();` –  Dec 15 '20 at 13:11
  • related : https://stackoverflow.com/questions/457316/combining-two-expressions-expressionfunct-bool – Drag and Drop Dec 15 '20 at 13:18

1 Answers1

3

There are two problems here. The first is that if you use Expression.And, this is equivalent to the binary operator &, which is probably not what you want. You have to write

Expression.AndAlso(e1, e2)

to get the equivalent of e1 && e2.

The bigger problem is that you are trying to combine two lambda expressions with &&, which is not possible because && works on boolean expressions. What you want is, given two lambda expressions lambda1 = x => e1 and lambda2 = x => e2, return x => e1 && e2.

In order to achieve this, follow these steps:

  1. Create a new ParameterExpression p.
  2. Replace the parameter in lambda1.Body by p (you can do this with an ExpressionVisitor) to get renamedBody1.
  3. Replace the parameter in lambda2.Body by p to get renamedBody2.
  4. Now you can construct
Expression.Lambda<Func<Item, bool>>(
    Expression.AndAlso(renamedBody1, renamedBody2), p);

This is the expression you can use as argument of the Where method.


Note: you can avoid the parameter renaming if you make sure that you use the same parameter expression while constructing the two lambda expressions, using Expression.Lambda(). But then you can't construct them as elegantly using the => syntax as you did in your code, which produces different parameters even though they are named item in both lambda expressions.


The full code

Alternatively, just use the And extension method in the following code (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;
            }
        }
    }
Mo B.
  • 5,307
  • 3
  • 25
  • 42