【问题标题】:What is the difference between creating a new object inside select LINQ clause and inside a method在 select LINQ 子句和方法中创建新对象有什么区别
【发布时间】:2019-12-09 22:27:43
【问题描述】:

我有一个映射到 SQL 表的实体类:

public class EntityItem {

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

}

我有以下两个sn-ps:

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 查询。此外,第二个 sn-p 的工作速度比第一个慢得多。正如我在 SQL 探查器中看到的那样,第二个 snipper 正在生成许多 SQL 查询。 所以我的问题:

  1. 为什么会这样?
  2. 如何像在第二个 sn-p 中一样创建新对象,但要避免 大量 SQL 查询?

【问题讨论】:

    标签: entity-framework linq linq-to-sql entity-framework-core


    【解决方案1】:

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

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

    例如,如果我执行:

    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 之前的 Extra .ToList() 调用造成的,这大致是过早执行将自动执行的操作。与第一个查询相比,这些查询的问题在于加载孩子的姓名时。在第一个查询中,生成的 SQL 在一个查询中提取父项和相关子项的详细信息。在其他情况下,查询将执行以提取父级的详细信息,但获取子级详细信息将构成延迟加载调用以获取更多详细信息,因为它将作为 Linq2Object 执行。

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

    【讨论】:

    • 在 EFCore 3 中,行为是抛出一个错误,说明它不能被翻译,并且需要实现查询。
    • 酷,朝着正确的方向退了一步。我刚刚阅读了那个重大变化,Select 仍然可以触发客户端评估(而Where 和这样的评估现在会抛出异常)docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/… 所以上面看起来仍然适用如果Select 人口被移交方法想要依赖延迟加载,则作为 EF Core 3 中的一个潜在问题。
    猜你喜欢
    • 2017-05-12
    • 2013-04-13
    • 1970-01-01
    • 2015-02-04
    • 1970-01-01
    • 1970-01-01
    • 2019-03-10
    • 1970-01-01
    • 2021-10-25
    相关资源
    最近更新 更多