构建自定义表达式时无法翻译 LINQ 表达式 'p'

The LINQ expression 'p' could not be translated when building custom expression

我有一个简单的订单class

public abstract class Entity
{
   public int Id { get; set; }
}

public class Order : Entity
{
    public string Description { get; set; }
    public string DeliveryAddress { get; set; }
    public decimal Price { get; set; }
    public int Priority { get; set; }
}

我有一些动态生成的过滤器,应该将其转换为表达式并针对数据库进行查询。

public class Filter<T>
    where T : Entity
{
    public Expression PropertySelector { get; set; }
    public Operator Operator { get; set; }
    public dynamic FilteringValue { get; set; }
}

public enum Operator
{
    Equal = 0,
    GreaterThan = 1,
}

现在,假设这是一个入口点和定义过滤器的地方(以及应该评估响应的地方)。

public IEnumerable<Order> GetByFilter()
{
    var filters = new List<Filter<Order>>()
        {
            new()
            {
                PropertySelector = ((Expression<Func<Order, int>>) (p => p.Priority)).Body,
                Operator = Operator.GreaterThan,
                FilteringValue = 1,
            },
            new()
            {
                PropertySelector = ((Expression<Func<Order, string>>) (p => p.Description)).Body,
                Operator = Operator.Equal,
                FilteringValue = "Laptop",
            }
        }

    IQueryable<Order> queryableOrders = _orderRepository.QueryAll();
    queryableOrders = _orderRepository.QueryByCustomerFilter(queryableOrders, filters);

    return queryableOrders.AsEnumerable();
}

我将过滤器传递给存储库中的某个方法...

public IQueryable<T> QueryByCustomerFilter(IQueryable<T> source, List<Filter<T>> filters)
{
    var entityType = source.ElementType;
    var predicate = PredicateBuilder.GetFilterPredicate(entityType, filters);

    return source.Where(predicate);
}

最后,我有一个 PredicateBuilder 负责根据指定的过滤器生成谓词...

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> GetFilterPredicate<T>(Type entityType, List<Filter<T>> filters)
        where T : Entity
    {
        var entity = Expression.Parameter(entityType, "p");
        var buildingBlocks = new List<Expression>();

        foreach (var filter in filters)
        {
            var left = filter.PropertySelector;
            var right = Expression.Constant(filter.FilteringValue);

            var buildingBlockExpression = filter.Operator switch
            {
                Operator.Equal => Expression.Equal(left, right),
                Operator.GreaterThan => Expression.GreaterThan(left, right),
                _ => throw new ArgumentOutOfRangeException(nameof(filter.Operator)),
            };

            buildingBlocks.Add(buildingBlockExpression);
        }

        var customFilterExpression = buildingBlocks.Aggregate(Expression.AndAlso);
        return Expression.Lambda<Func<T, bool>>(customFilterExpression, entity);
    }
}

但是当我 运行 这段代码时,我得到:

System.InvalidOperationException: '无法翻译 LINQ 表达式 'p'。以可以翻译的形式重写查询,或者通过插入对 'AsEnumerable'、'AsAsyncEnumerable'、'ToList' 或 'ToListAsync'.[= 的调用显式切换到客户端评估49=]

然而,让我感到困惑的是。如果我将下一行代码放在 QueryByCustomerFilter 方法中,我可以清楚地看到为该行代码编写的表达式与代码基于过滤器生成的表达式没有区别。

var testQueryable = Context.Orders.Where(p => p.Priority > 1 && p.Description == "Laptop").AsQueryable();

SQL 两个表达式的查询是相同的,我看不出有什么区别。

"SELECT [o].[Id], [o].[DeliveryAddress], [o].[Description], [o].[Price], [o].[Priority]\r\nFROM [Orders] AS [o]\r\nWHERE ([o].[Priority] > 1) AND ([o].[Description] = N'Laptop')"

最后,最令人困惑的部分是如果我这样做

testQueryable.ToList()

在评估原始查询之前,一切都会按预期进行。因此,两个表达式都已成功翻译,我能够得到预期的结果。

那么,这里发生了什么?为什么无法翻译原始表达式以及示例中的两个可查询项如何相互连接?

您通过 var parameterReplacerVisitor = new ParameterReplacerVisitor(entity); 创建的参数与您在 PropertySelector 中的表达式中的参数不同,尽管名称相同。您需要用创建的替换传入。例如:

class ParameterReplacerVisitor : ExpressionVisitor
{
    private readonly ParameterExpression _param;

    public ParameterReplacerVisitor(ParameterExpression expression)
    {
        _param = expression;
    }

    protected override Expression VisitParameter(ParameterExpression node) => _param;
}
public static Expression<Func<T, bool>> GetFilterPredicate<T>(Type entityType, List<Filter<T>> filters)
    where T : Entity
{
    var entity = Expression.Parameter(entityType, "p");
    var buildingBlocks = new List<Expression>();

    foreach (var filter in filters)
    { 
        var parameterReplacerVisitor = new ParameterReplacerVisitor(entity);
        
        var left = filter.PropertySelector;
        left = parameterReplacerVisitor.Visit(left);
        ....
     }
}