将复杂的 IQueryable LINQ 重构为子方法

Refactor complex IQueryable LINQ into sub methods

我们继承了另一位开发人员的解决方案,其中我们有一个方法可以为 api 调用生成复杂的产品结构。此处的模型(Product)是通过 IQueryable 从 Entity Framework 获取的,DTO 是在一个跨越大约 230 行代码的巨大 LINQ 语句中生成的,这使得它几乎无法调试。 我们正在 运行 EF Core 3.1.3 仅供参考。

我想重构这段代码,以确保我们更好地理解代码中可能出现的问题,但我似乎 运行 遇到了一个问题,这个问题可能与我对如何使用IQueryables.

为了简单起见,我以代码中最简单的部分为例。为了便于阅读,省略了其余部分。

private async Task<List<HomePageProductModel>> FindProducts(IQueryable<Data.Entities.Product> products, string priceBehaviour = "DefaultPrice")
        {
            var productModel = products.Select(p => new HomePageProductModel
            {
                Id = p.ProductId,
                Segment = p.Segment.Name,
                StartDate = p.StartDate,
                EndDate = p.EndDate,
                Type = p.ProductType.Name,
                Title = p.Title,
                Teaser = p.Teaser,
                Description = p.Description,
                TermsAndConditionsLink = p.Prices.SelectMany(p => p.PriceBehaviours.Where(p => p.PriceBehaviourType.Name == "TermsAndConditionsUrl")).Select(p => p.PriceBehaviourType.Value).FirstOrDefault(),

我想提取生成 TermsAndConditionsLink 的部分。我的第一个目标是将所有内容提取为方法,然后让它执行相同的 LINQ selectMany

TermsAndConditionsLink = TermsAndConditionsLink(p)


private static string TermsAndConditionsLink(Product p)
{
    return p.Prices.SelectMany(p => p.PriceBehaviours.Where(p => p.PriceBehaviourType.Name == "TermsAndConditionsUrl")).Select(p => p.PriceBehaviourType.Value).FirstOrDefault();
}

编译,但在 p.PriceBehaviours 上抛出 NullRefence,因为列表现在不再是 IQueryable,而是一个列表,因为模型的生成方式和 PriceBehaviours 不再包含在内。

我在摆弄它一个小时左右后确实让它工作了,但我不确定我现在是否已经在同一个对象上创建了多个迭代,或者是否像我所做的那样在性能方面做得很好。

TermsAndConditionsLink = GetTermsAndConditionsLink(products)

private static string GetTermsAndConditionsLink(IQueryable<Product> product)
{
     return product.SelectMany(pr => pr.Prices).SelectMany(p => p.PriceBehaviours.Where(p => p.PriceBehaviourType.Name == "TermsAndConditionsUrl")).Select(p => p.PriceBehaviourType.Value).FirstOrDefault();
}

模型如下(大量简化为仅相关部分):

public class Product
{
    public int ProductId { get; set; }

    public List<Price> Prices { get; set; }
}

public class Price
{
    public int PriceId { get; set; }
    public List<PriceBehaviour> PriceBehaviours { get; set; }
}

public class PriceBehaviour // relation
{
    public int PriceBehaviourId { get; set; }
    public Price Price { get; set; }
    public PriceBehaviourType PriceBehaviourType { get; set; }
}

有没有人有任何让它变得更好的建议,或者我在这里取得的成就,性能方面还好吗?

您不能以那种方式提取 TermsAndConditionsLink。 EF 无法查看方法主体并创建所需的查询。 此类任务有第三方扩展:https://github.com/axelheer/nein-linq

所以你的方法应该按以下方式重写:


[InjectLambda]
private static string TermsAndConditionsLink(Product p)
{
    throw new NotImplementedException();
}

private static Expression<Func<Product, string>> TermsAndConditionsLink()
{
    return p => p.Prices.SelectMany(p => p.PriceBehaviours
          .Where(p => p.PriceBehaviourType.Name == "TermsAndConditionsUrl"))
       .Select(p => p.PriceBehaviourType.Value)
       .FirstOrDefault();
}

并且不要忘记在查询顶部调用 .ToInjectable()

你也可以用另一个库实现类似的结果https://github.com/hazzik/DelegateDecompiler