我有以下实体:
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 ???
我需要从Name
和Description
获得ProductTypeLocalization
和Language == "en"
。
我可以在每个上使用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();