在IGrouping中使用Where(表达式<Func <T,bool >>)

Men*_*vel 5 c# linq linq-to-entities

考虑以下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();
Run Code Online (Sandbox Code Playgroud)

如果你像我一样,你的脚趾会重复产品选择逻辑.这种模式也在另一个地方重复.我首先尝试用IEnumerable上的扩展方法替换它,这当然不起作用:Linq to Entities需要一个Expression来解析和翻译.
所以我创建了这个方法:

    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)
                ));
    }
Run Code Online (Sandbox Code Playgroud)

以下选择现在完全正常:

var test = db.Leads.Where(Extensions.ContainingProductEx("productA")).ToList();
Run Code Online (Sandbox Code Playgroud)

但是,这不会编译,因为IGrouping不包含Where接受Expression的覆盖:

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();
Run Code Online (Sandbox Code Playgroud)

将g转换为IQueryable编译,但随后产生"内部.NET Framework数据提供程序错误1025".

有没有办法将这个逻辑包装在自己的方法中?

Ser*_*rvy 2

这是一个可以使用 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)
            ));
}
Run Code Online (Sandbox Code Playgroud)

接下来,我们需要在声明查询之前调用该方法:

var predicate = Extensions.ContainingProductEx();
Run Code Online (Sandbox Code Playgroud)

您的查询现在可以写为:

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()
Run Code Online (Sandbox Code Playgroud)