过滤一个实体的子集合

Filter child collection of one entity

我有以下实体:

public class Product {
  public Int32 ProductId { get; set; }
  public Double Price { get; set; }
  public virtual ProductType ProductType { get; set; }
}

public class ProductType {
  public Int32 ProductTypeId { get; set; }
  public virtual ICollection<ProductTypeLocalization> ProductTypeLocalizations { get; set; }
}

public class ProductTypeLocalization {
  public Int32 ProductTypeId { get; set; }
  public String Language { get; set; }
  public String Name { get; set; }
  public String Description { get; set; }
  public virtual ProductType { get; set; }
}

那么我有一个查询如下:

var models = await products.Select(product => new {
  Id = product.Id,
  Price = product.Price,
  ProductType = new {
    Id = product.ProductType.ProductTypeId,
    Name = ???,
    Description = ???
  }
}).ToListAsync()

在我的查询中显示

Name = ???,
Description ???

我需要用 Language == "en"ProductTypeLocalization 得到 NameDescription

我可以对每个都使用 FirstOrDefault,但我认为这不是一种有效的方法。

最好的方法是什么?

LEFT OUTER JOIN 翻译似乎最适合这种情况。

理论上 EF Core 查询转换器应该能够将通用 FirstOrDefault() 表达式合并为单个 LEFT OUTER JOIN,就像它对可选参考导航属性所做的那样。

实际上(截至目前最新的 EF Core 2.2)它不会这样做,而是为每个选定字段生成单独的相关子查询。

假设每个产品类型针对特定语言有 0 或 1 个本地化版本,可以使用 SelectMany 实现所需的翻译,如下所示:

var models = await products.SelectMany(
    product => product.ProductType.ProductTypeLocalizations
        .DefaultIfEmpty()
        .Where(ptl => ptl == null || ptl.Language == "en"),
    (product, ptl) => new
    {
        Id = product.ProductId,
        Price = product.Price,
        ProductType = new
        {
            Id = product.ProductType.ProductTypeId,
            Name = ptl.Name,
            Description = ptl.Description
        }
    })
    .ToListAsync();

或使用 LINQ 查询语法的等效且可读性更好的版本:

var models = await (
    from product in products
    let pt = product.ProductType
    from ptl in pt.ProductTypeLocalizations.DefaultIfEmpty()
    where ptl == null || ptl.Language == "en"
    select new
    {
        Id = product.ProductId,
        Price = product.Price,
        ProductType = new
        {
            Id = pt.ProductTypeId,
            Name = ptl.Name,
            Description = ptl.Description
        }
    }).ToListAsync();