.Net c#application - 解决与Entity框架中的更改跟踪相关的性能问题的正确方法

时间:2017-07-24 18:44:15

标签: c# .net performance entity-framework

我们有一个使用代码优先方法的Web MVC .NET应用程序。执行基本CRUD操作时,一切都正常,但我们的报告和长时间运行的任务将永远完成。

我们的大多数有问题的案例都可以通过以下两种情况恢复:

  1. 有一项任务可以迭代10,000多个项目(使用sqlDataReader),制作一些 更改并为每个人创建一个副本。
  2. 我们有报告迭代100,000多个项目(使用sqlDataReader)来读取一些信息并写入文件。
  3. 在意识到处理我们的任务和报告所需的时间呈指数级增长(O(n^2))与要处理的项目数量之后,我们进一步调查并找出问题的根源:实体框架中的变更跟踪。

    如果我们停用AutoDetectChangesEnabled,一切正常,但我们在该主题上阅读的每篇文章都说我们应该尽可能避免这样做。

    所以这是我的问题:是否禁用AutoDetectChangesEnabled解决这类问题的唯一或推荐方法?还有其他清洁解决方案吗?你会怎么做到这一点?

    这假设我们将遵循本系列文章中所述的所有建议:https://blog.oneunicorn.com/2012/03/10/secrets-of-detectchanges-part-1-what-does-detectchanges-do/

    这是一个小代码示例,说明了方案2:

    //SQL query to fetch 100,000+ items
    SqlCommand command = new SqlCommand(query, conn);
    
    //Use SqlDataReader to stream results
    using (SqlDataReader reader = command.ExecuteReader())
    {
        /*In the following loop, if we use SqlCommand instead of LINQ, it's fast and steady. 
          But as soon as we start using LINQ, each iteration takes exponentially more time 
          to complete - even if we are only making read operations. Unless we disable 
          AutoDetectChangesEnabled: then it's fast and steady.*/
        while (reader.Read())
        {
            /*Fetch additional information. I know this could be done with a join in the 
              original query but we have a lot of additional info to fetch and the query would 
              be too heavy (this is a simplified version of the code).*/
            Item item = db.Items.Where(x => x.OtherItem_Id == (int)reader["Id"]).FirstOrDefault();
    
            //Read properties
            prop1 = item.prop1;
            prop2 = item.prop2;
            prop3 = item.prop3;
    
            //Use those properties to add an entry to a csv file
        }
    }
    

    谢谢!

2 个答案:

答案 0 :(得分:1)

如果没有具体的例子,目前很难回答你的问题。正如Brian在评论部分所说,他们可以拥有无​​数可能的解决方案......

例如,一个解决方案可以像使用AddRange一样简单,而不是每次在DataReader中循环时添加实体。

var listA = new List<A>();
while(dr.Read())
{
    var itemA = //...code...

    // ctx.Add(itemA); // DON'T do it...
    list.Add(itemA);
}

ctx.AddRange(listA);

DetectChanges方法只会调用一次而不是X次。

免责声明:我是该项目的所有者Entity Framework Extensions

另一个解决方案可能是我最近收到的同样问题。由于拥有一个非常复杂和大型的模型,检测变更对于某人来说需要1个多小时。

我们只是建议他直接从我们的库中使用BulkInsert插入:Entity Framework Extensions,它将性能提高了50倍以上,而不是使用SaveChanges。

编辑:回答子问题

  

启用AutoDetectChanges时,即使读取操作也会减慢速度

像find这样的读取方法会自动调用DetectChanges,所以是的,一些读操作也会受到DetectChanges的影响

public TEntity Find(params object[] keyValues)
{
  this.InternalContext.ObjectContext.AsyncMonitor.EnsureNotEntered();
  this.InternalContext.DetectChanges(false);
  // ...code
}

答案 1 :(得分:1)

可能有多种方法可以提高性能,但正如上面所述,它很难帮助解决这类问题,因为代码的其他部分可能影响到我们无法看到。话虽如此,这里有一些想法。

1 - 您的EF LINQ查询位于100,000个项目的自描述循环中。这意味着您将在此循环期间向数据库发出100,000个查询。根据{{​​1}}表大小和复杂性,您可以将所有这些记录放入内存中,然后在内存列表中执行LINQ命令。这会使你的SQL查询减少99,999。

Items

2 - 您可以直接在查询中触发using (SqlDataReader reader = command.ExecuteReader()) { //pull the entire table to an in-memory list. var items = db.Items.ToList(); while (reader.Read()) { Item item = items.Where(x => x.OtherItem_Id == (int)reader["Id"]).FirstOrDefault(); prop1 = item.prop1; prop2 = item.prop2; prop3 = item.prop3; } } 。如果这是真正的只读数据集,那么你就不会有任何危险,因为你没有打电话给NoTracking

db.SaveChanges()