【发布时间】:2022-01-19 12:31:07
【问题描述】:
我有一个简单的订单类
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”的调用显式切换到客户端评估。
但是,这让我感到困惑。如果我将下一行代码放在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()
在评估原始查询之前,一切都会按预期工作。所以,两个表达式都成功翻译了,我能够得到预期的结果。
那么,这里发生了什么?为什么原始表达式无法翻译?示例中的两个可查询对象如何相互连接?
【问题讨论】:
标签: c# sql-server entity-framework entity-framework-core iqueryable