在 IGrouping 中使用 Where( Expression> )
本文关键字:func bool Expression IGrouping Where | 更新日期: 2023-09-27 18:34:38
请考虑以下 Linq to Entities 查询:
return (from lead in db.Leads
join postcodeEnProvincie in postcodeEnProvincies
on lead.Postcode equals postcodeEnProvincie.Postcode
where (lead.CreationDate >= range.StartDate) && (lead.CreationDate <= range.EndDate)
group lead by postcodeEnProvincie.Provincie into g
select new Web.Models.GroupedLeads() {
GroupName = g.Key,
HotLeads = g.Count(l => l.Type == Data.LeadType.Hot),
Leads = g.Count(),
PriorityLeads = g.Count(l => l.Type == Data.LeadType.Priority),
Sales = g.Count(l => l.Sold),
ProductA = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productA", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productA", StringComparison.CurrentCultureIgnoreCase))))),
ProductB = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productB", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productB", StringComparison.CurrentCultureIgnoreCase))))),
ProductC = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productC", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productC", StringComparison.CurrentCultureIgnoreCase))))),
ProductC = g.Count(l => l.Producten.Any(a => ((a.Name.Equals("productD", StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals("productD", StringComparison.CurrentCultureIgnoreCase)))))
}).ToList();
如果你像我一样,你的脚趾会因产品选择逻辑的重复而卷曲。这种模式也在另一个地方重复。我首先尝试用IEnumerable上的扩展方法替换它,这当然不起作用:Linq to Entities需要一个表达式来解析和翻译。
所以我创建了这个方法:
public static System.Linq.Expressions.Expression<Func<Data.Lead, bool>> ContainingProductEx(string productName)
{
var ignoreCase = StringComparison.CurrentCultureIgnoreCase;
return (Data.Lead lead) =>
lead.Producten.Any(
(product =>
product.Name.Equals(productName, ignoreCase) ||
product.Parent.Name.Equals(productName, ignoreCase)
));
}
以下选择现在工作正常:
var test = db.Leads.Where(Extensions.ContainingProductEx("productA")).ToList();
但是,这不会编译,因为 IGrouping 不包含接受表达式的 Where 的覆盖:
return (from lead in db.Leads
join postcodeEnProvincie in postcodeEnProvincies
on lead.Postcode equals postcodeEnProvincie.Postcode
where (lead.CreationDate >= range.StartDate) && (lead.CreationDate <= range.EndDate)
group lead by postcodeEnProvincie.Provincie into g
select new Web.Models.GroupedLeads()
{
GroupName = g.Key,
HotLeads = g
.Where(l => l.Type == Data.LeadType.Hot)
.Count(),
Leads = g.Count(),
PriorityLeads = g
.Where(l => l.Type == Data.LeadType.Priority)
.Count(),
Sales = g
.Where(l => l.Sold)
.Count(),
ProductA = g
.Where(Extensions.ContainingProductEx("productA"))
.Count(),
ProductB = g
.Where(Extensions.ContainingProductEx("productB"))
.Count(),
ProductC = g
.Where(Extensions.ContainingProductEx("productC"))
.Count(),
ProductD = g
.Where(Extensions.ContainingProductEx("productD"))
.Count()
}).ToList();
将 g 强制转换为 IQueryable 编译,但随后生成"内部 .NET Framework 数据提供程序错误 1025"。
有没有办法用自己的方法包装这个逻辑?
这是一个可以使用 LINQKit 解决的问题。 它允许从其他表达式中调用表达式,并将调用的表达式内联到其调用方中。 可悲的是,它只支持少数非常具体的情况,因此我们需要稍微调整一下您的表达式生成方法。
我们不会将产品名称传递给表达式生成方法,而是将其作为返回表达式的参数:
public static Expression<Func<Data.Lead, string, bool>> ContainingProductEx()
{
var ignoreCase = StringComparison.CurrentCultureIgnoreCase;
return (lead, productName) =>
lead.Producten.Any(
(product =>
product.Name.Equals(productName, ignoreCase) ||
product.Parent.Name.Equals(productName, ignoreCase)
));
}
接下来,我们需要在声明查询之前调用该方法:
var predicate = Extensions.ContainingProductEx();
您的查询现在可以编写为:
from lead in db.Leads.AsExpandable()
//...
ProductA = g
.Where(lead => predicate.Invoke(lead, "productA"))
.Count(),
ProductB = g
.Where(lead => predicate.Invoke(lead, "productB"))
.Count(),
ProductC = g
.Where(lead => predicate.Invoke(lead, "productC"))
.Count(),
ProductD = g
.Where(lead => predicate.Invoke(lead, "productD"))
.Count()
与其担心在查询中创建可以引用的函数指针/表达式(可能是不可能的(,为什么不创建一个单独的私有方法,该方法采用IEnumerable<Lead>
、字符串并返回 int 并在查询中引用方法组?我认为您的困惑源于尝试在集合上创建一个扩展方法,而不是在集合中创建一种方法和您正在寻找的值。
像这样:
ProductA = GetLeadsForProduct(g, "productA")
private int GetLeadsForProduct(IEnumerable<Lead> leads, string productType)
{
return leads.Count(l => l.Producten.Any(a => ((a.Name.Equals(productType, StringComparison.CurrentCultureIgnoreCase)) || (a.Parent.Name.Equals(productType, StringComparison.CurrentCultureIgnoreCase)))))
}