在 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 查询。
所以我的问题:
- 为什么会这样?
- 如何像第二个片段中那样创建新对象,但要避免
很多 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
方法来填充您的视图模型。这将自动放置映射代码,以便它像第一个场景一样工作,而无需您写出所有映射代码。
我有一个实体 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 查询。 所以我的问题:
- 为什么会这样?
- 如何像第二个片段中那样创建新对象,但要避免 很多 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
方法来填充您的视图模型。这将自动放置映射代码,以便它像第一个场景一样工作,而无需您写出所有映射代码。