将“Linq”转为“实体聚合”并围绕种子

本文关键字:实体聚合 种子 Linq 转为 实体 | 更新日期: 2023-09-27 18:21:46

使用Entity Framework 6,我正在寻找等效的Aggregate——接受种子的重载——但它从种子而不是聚合集合扩展。也就是说,给定Expression<Func<T, bool>>的集合(适合在Where中使用),我希望将它们依次应用于基础IQueryable

注意:尽管在Linq-to-Entities中Aggregate本身是不受支持的,但诀窍是我们并没有试图将Aggregate操作投影到SQL中,而是使用IEnumerable方法Aggregate将where条件添加到IQueryable中。因此,只要IQueryable是种子,而不是扩展方法的this参数,Aggregate就可以与EF一起工作。

因此,给定这个设置:

public class Product {
    public int Name { get; set; }
    public int Size { get; set; }
    public int Price { get; set; }
}
var products = new List<Product> {
    new Product { Name = "toaster", Size = 100, Price = 49 },
    new Product { Name = "pencil", Size = 2, Price = 5 },
    new Product { Name = "Oven", Size = 500, Price = 1000 }
}.AsEnumerable(); // please pretend this is an `IQueryable`
var conditions = new List<Func<Product, bool>> {
    { o => o.Size > 50 },
    { o => o.Price < 100 }
}.AsEnumerable(); // please pretend this is of Expression<Func<Product, bool>>

代替:

IQueryable<Product> filtered =
   conditions.Aggregate(queryable, (current, condition) => current.Where(condition));

我希望能够使用:

IQueryable<Product> filtered = queryable.WhereAll(conditions);

这是我只需要编写自己的扩展方法就可以实现的吗?或者有没有一种方法可以通过本地Linq-to-Entities扩展方法实现这一点?我认为Aggregate语法在没有发生真正聚合的情况下可能会令人困惑(至少,开发人员会如何看待它)。如果我确实使用了Aggregate,我认为这在代码中会令人惊讶或困惑,首先是因为没有发生真正的聚合(例如求和或级联),其次是因为当conditions是空集合时,它仍然返回种子(对于处理queryables/collections和扩展方法来说,这是一个有点不惯用且令人惊讶的结果,其中返回值不是扩展方法操作的对象的转换)。

我愿意接受比WhereAll更好的名字。我已经考虑了TransformMapApply

如果这只是针对IEnumerables和Linq-to-Objects,那么答案很简单:

products.Where(p => conditions.All(c => c(p)));

但是,这不能投影到SQL for Linq to Entities中,因为您不能手动处理lambda中的表达式,以将该表达式正确添加到父表达式树中(表达式本身必须是整个lambda),除非您想对具有ExpressionVisitor的表达式执行复杂的运算(对此操作过度)。

将“Linq”转为“实体聚合”并围绕种子

编写一个扩展方法很简单。。

public static IQueryable<T> WhereAll<T>(this IQueryable<T> q,IEnumerable<Expression<Func<T, bool>>> conditions)
{
  foreach(var condition in conditions)
    q=q.Where(condition);
  return q;
}

您也可以使用谓词生成器。在所有条件下初始化为True和AND,然后将正则.Where与您构建的复合谓词一起使用。