【问题标题】:Enumerable.Any() is very Slow on Large Linq Data SetEnumerable.Any() 在大型 Linq 数据集上非常慢
【发布时间】:2017-05-05 19:18:20
【问题描述】:

我正在运行这样的代码:

var somethings = db.Somethings.Select(s => new SomethingViewModel
{
    Id = s.Id,
    Name = s.Name,
    IsActive = s.IsActive
    SubSomethings = s.SubSomethings.Select(ss => new SubSomethingViewModel
    {
        Id = ss.Id,
        Name = ss.Name,
        IsActive = ss.IsActive
    }).Where(wss => wss.IsActive)                        
}).Where(ws => ws.IsActive && (ws.SubSomethings.Any())) //remove elements if no SubSomethings
.ToList();

如您所见,这是一对多的关系。 Something 中有一个 SubSomethings 的列表。如果我取出 && (ws.SubSomethings.Any()),我会得到一个非常快的列表返回。

但是,我只想在列表中包含至少有一个 SubSomething 的Somethings。我还尝试了以下方法并获得了同样可怕的效率:

var somethings = db.Somethings.Select(s => new SomethingViewModel
{
    Id = s.Id,
    Name = s.Name,
    IsActive = s.IsActive
    SubSomethings = s.SubSomethings.Select(ss => new SubSomethingViewModel
    {
        Id = ss.Id,
        Name = ss.Name,
        IsActive = ss.IsActive
    }).Where(wss => wss.IsActive)                        
}).Where(ws => ws.IsActive)
.ToList(); //this finishes very quickly

var somethings2 = somethings.Where(s => s.SubSomethings.Any()).ToList(); //This is where the code bogged down

如何重新编写查询以使陷入困境的代码更快?需要注意的一件事:这适用于一两条记录。当我达到超过 8000 条记录时,至少需要四分钟。

这是我在 SubSomething 表上为SomethingId的外键创建的索引,对应Something.Id

CREATE NONCLUSTERED INDEX [IX_SubSomething_SomethingId] ON [dbo].[SubSomething]
(
    [SomethingId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
GO

这里是SubSomething.SomethingId的外键创建:

ALTER TABLE [dbo].[SubSomething]  WITH CHECK ADD  CONSTRAINT [FK_SubSomething_Something_SomethingId] FOREIGN KEY([SomethingId])
REFERENCES [dbo].[Something] ([Id])
GO

ALTER TABLE [dbo].[SubSomething] CHECK CONSTRAINT [FK_SubSomething_Something_SomethingId]
GO

【问题讨论】:

  • @Gusman - 因为 Count 需要消耗整个数据,所以速度会变慢
  • 发送到服务器的查询是什么样的?在您的第一个版本上执行 .ToString() 而不是 .ToList() 来获取它。如果您在 SSMS 中运行它,查询计划又会是什么样子?
  • @GiladGreen 不,它将转换为 SQL Count,它将使用主键索引的长度。
  • 您可能应该在两个查询中获取数据。
  • @Gusman 它不能转换为 SQL,至少不能转换为 OP 的第二种形式。 .Any() 在一个仅使用 Enumerable 方法的表达式中,没有任何东西可供 Queryable 操作。

标签: c# .net entity-framework linq entity-framework-core


【解决方案1】:

EF Core 是您的问题。目前已知当查询包含子集合投影时会执行 N + 1 个子查询。

解决这个问题并将整个事情限制为 2 个 SQL 查询的唯一方法是尽可能多地过滤,然后使用预先加载在内存中加载带有子实体的整个实体集,然后切换到 LINQ to Objects 并执行最终的投影/过滤:

var somethings = db.Somethings
    .Include(s => s.SubSomethings)
    .Where(s => s.IsActive)
    .AsEnumerable()
    .Select(s => new SomethingViewModel
    {
        Id = s.Id,
        Name = s.Name,
        IsActive = s.IsActive,
        SubSomethings = s.SubSomethings.Select(ss => new SubSomethingViewModel
        {
            Id = ss.Id,
            Name = ss.Name,
            IsActive = ss.IsActive
        }).Where(wss => wss.IsActive).ToList()
    })
    .Where(s => s.SubSomethings.Any())
    .ToList();

【讨论】:

    【解决方案2】:

    我认为问题在于您将第一个查询转换为列表。试试这个:

    var products = db.Products.Where(ws => ws.IsActive && ws.SubSomethings.Count(ss => ss.IsActive) > 0)
    .Select(p => new SomethingViewModel
    {
        Id = p.Id,
        Name = p.Name,
        IsActive = p.IsActive,
        SubSomethings = p.SubSomethings.Select(ss => new PartViewModel
        {
            Id = ss.Id,
            Name = ss.Name,
            IsActive = ss.IsActive
        }).Where(wss => wss.IsActive)   
    }).ToList();
    

    【讨论】:

    • 这只是将 OP 的第二次尝试替换为 OP 的第一次尝试,不是吗?
    • 是的。我看不出有什么不同。
    • 是的。我正在考虑这个问题,我认为在返回结果之前你不需要投影这些字段。我正在修复。
    • 那里。这应该会导致所有连接和过滤都发生在服务器端。
    • 这实际上非常有效。它确实突出了我做事有点倒退。
    【解决方案3】:

    我相信每个Something 的SubSomethings 属性仍然是一个可枚举的,直到您的最后一行代码才执行其基础数据库查询。因此,在该行中,您正在为每个 Something 按顺序执行 1 个数据库查询。

    我建议在您的初始查询中使用 SubSomethings 的 Include(),以便通过内部连接将您的所有数据一起获取:

    var somethings = db.Somethings
        .Include("SubSomethings") // added this
        .Select(s => new SomethingViewModel
    {
        Id = s.Id,
        Name = s.Name,
        IsActive = s.IsActive
        SubSomethings = s.SubSomethings.Select(ss => new SubSomethingViewModel
        {
            Id = ss.Id,
            Name = ss.Name,
            IsActive = ss.IsActive
        }).Where(wss => wss.IsActive)                        
    }).Where(ws => ws.IsActive)
    .ToList(); //this finishes very quickly
    
    var somethings2 = somethings.Where(s => s.SubSomethings.Any()).ToList(); //This is where the code bogged down
    

    【讨论】:

    • 你能举个例子吗?
    • 已添加代码。如果这不起作用,您可以尝试在分配给 SubSomethings 属性时另外执行 ToList()。
    【解决方案4】:

    正如 Ivan Stoev 所说,EF Core 是你的问题,我分享了一个不使用 LINQ to SQL 的替代方案,但它不能提供更好的性能:

    1. 在数据库中创建一个视图,其中包含连接所需数据的逻辑
    2. 使用db.Database.SqlQuery("Query") 查询视图并返回数据:

      var results = db.Database.SqlQuery("Select ColA,ColB,ColC from ViewSomething")

      SomethingViewModel 对象必须是一个强类型,其属性与视图的 cols 匹配。

    我知道这并不漂亮,并且对视图进行了硬编码查询,但通常是您通过编译器后使用 EF 获得的最快性能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-31
      • 2017-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-07
      • 2012-12-18
      相关资源
      最近更新 更多