在 select LINQ 子句中创建新对象和在方法中创建新对象有什么区别

What is the difference between creating a new object inside select LINQ clause and inside a method

我有一个实体 class 映射到 SQL table:

public class EntityItem {

 public virtual ICollection<EntityItem2> SomeItems { get; set; }

}

我有以下两个片段:

var items = _repository.Table.Where(x => x.Id == id)
                        .Select(x => new ItemModel {
                                     Items = x.SomeItems.Select(y => new SomeItem { //mapping is here...}).ToList() 
                               });

var items = _repository.Table.Where(x => x.Id == id).Select(x => someModelMapper.BuildModel(x));

//inside a mapper
public ItemModel BuildModel(EntityType entity){

    var model = new ItemModel();
    model.Items = entity.SomeItems.Select(x => anotherMapper.BuildModel(x));

    return model;
}

因此,我在这两种情况下收到了不同的 SQL 查询。此外,第二个片段比第一个片段运行得慢得多。正如我在 SQL 分析器中看到的那样,第二个剪接器正在生成许多 SQL 查询。 所以我的问题:

  1. 为什么会这样?
  2. 如何像第二个片段中那样创建新对象,但要避免 很多 SQL 个查询?

您看到性能差异的可能原因是 EF Core 过早地实现了查询。编译 Linq 语句时,EF 会尝试将其转换为 SQL。如果您在表达式中调用函数,EF6 会引发异常,导致该方法无法转换为 SQL。 EF Core 试图变得聪明,当它遇到无法转换的方法时,它会执行查询直到它可以到达的点,然后继续执行其余的作为 Linq2Object,你的方法可以 运行。在我看来,这是一个非常愚蠢的功能,代表着巨大的性能地雷,虽然可以将其作为一个可能的选项提供,但默认情况下应将其禁用。

由于主查询 运行 之后的延迟加载,您可能会看到额外的查询,以在映射方法中填充视图模型。

例如,如果我执行:

var results = context.Parents.Select(x => new ParentViewModel
{
    ParentId = x.ParentId,
    Name = x.Name,
    OldestChildName = x.Children.OrderByDescending(c => c.BirthDate).Select(c => c.Name).FirstOrDefault() ?? "No Child"
}).Single(x => x.ParentId == parentId);

那将作为一条语句执行。调用方法来填充视图模型:

var results = context.Parents 
    .Select(x => buildParentViewModel(x))
    .Single(x => x.ParentId == parentId);

会执行类似的东西:

var results = context.Parents
    .ToList()
    .Select(x => new ParentViewModel
    {
        ParentId = x.ParentId,
        Name = x.Name,
        OldestChildName = x.Children.OrderByDescending(c => c.BirthDate).Select(c => 
c.Name).FirstOrDefault() ?? "No Child"
    }).Single(x => x.ParentId == parentId);

最差或:

var results = context.Parents
    .Where(x => x.ParentId == parentId)
    .ToList()
    .Select(x => new ParentViewModel
    {
        ParentId = x.ParentId,
        Name = x.Name,
        OldestChildName = x.Children.OrderByDescending(c => c.BirthDate).Select(c => 
c.Name).FirstOrDefault() ?? "No Child"
    }).Single();

...充其量。这些是由于 Select 之前的额外 .ToList() 调用,这大致是过早执行将自动执行的操作。与第一个查询相比,这些查询的问题在于加载 child 的名称时。在第一个查询中,生成的 SQL 在一个查询中提取 parent 和相关 child 的详细信息。在替代情况下,查询将执行以提取 parent 的详细信息,但获取 child 详细信息将构成延迟加载调用以获取更多详细信息,因为它将作为 Linq2Object.

解决方案是使用 Automapper,它内置于 ProjectTo 方法来填充您的视图模型。这将自动放置映射代码,以便它像第一个场景一样工作,而无需您写出所有映射代码。