将复杂的 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
我们继承了另一位开发人员的解决方案,其中我们有一个方法可以为 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