简体   繁体   English

C# .NET 4.6.1 实体框架 - 尽管没有调用 DB.SaveChanges(),但 DB.MyTable.Add(...) 还是很慢

[英]C# .NET 4.6.1 Entity Framework - DB.MyTable.Add(...) is slow despite NOT calling DB.SaveChanges()

(A) This version is slow ... duration is measured in multiple minutes (A)这个版本很……持续时间以分钟计算

  • DB is a typical EF Data Context to a SQL Serve database DB 是 SQL Server 数据库的典型 EF 数据上下文
  • AA_Words_100 is a simple SQL Server table which is added to the EF designer AA_Words_100是添加到 EF 设计器的简单 SQL Server 表
  • DB.AA_Words_100.Add is called ~3,000 times (confirmed via debugging with counter variables) DB.AA_Words_100.Add被调用约 3,000 次(通过使用计数器变量调试确认)
  • I have confirmed that >99% of the runtime is inside the inner loop我已经确认 >99% 的运行时间在内部循环内
  • XCampaign is a Dictionary<string, Dictionary<string, _Word>> where _Word is a trivial non-EF object. XCampaign是一个Dictionary<string, Dictionary<string, _Word>> ,其中 _Word 是一个简单的非 EF 对象。
foreach (var XCampaign in Words100)
    foreach (var KVP in XCampaign.Value)
        DB.AA_Words_100.Add(KVP.Value.To_AA_Word_100());

DB.SaveChanges();

(B) This version is fast ... - .Add() is simply commented out to narrow the scope (B)这个版本很快...... - .Add()只是被注释掉以缩小范围

var iTemp = 0;

foreach (var XCampaign in Words100)
  foreach (var KVP in XCampaign.Value)
    iTemp++;

DB.SaveChanges();

(C) This version is fast . (C)这个版本很快 I simply fill up a List before calling DB.AddRange(...)我只是在调用DB.AddRange(...)之前填写一个 List

var LIST_WordsToAdd = new List<AA_Words_100>();

foreach (var XCampaign in Words100)
{
    foreach (var KVP in XCampaign.Value)
    {
        LIST_WordsToAdd.Add(KVP.Value.To_AA_Word_100());
    }

    DB.AA_Words_100.AddRange(LIST_WordsToAdd);
}

DB.SaveChanges();

(D) Documentation (D)文件

According to DbContext.Add documentation根据DbContext.Add文档

Begins tracking the given entity, and any other reachable entities that are not already being tracked, in the Added state such that they will be inserted into the database when SaveChanges() is called.开始跟踪给定实体,以及任何其他尚未被跟踪的可访问实体,处于已添加状态,以便在调用 SaveChanges() 时将它们插入到数据库中。

In particular, when SaveChanges() is called.特别是在调用SaveChanges()时。

I recently migrated to EF from Linq-to-SQL in this application.我最近在这个应用程序中从 Linq-to-SQL 迁移到 EF。 Linq-to-SQL did not have this problem. Linq-to-SQL 没有这个问题。

What reason could there be for the DB.AA_Words_100.Add(...) command being so slow? DB.AA_Words_100.Add(...)命令如此缓慢的原因是什么?

Thank you!谢谢!

#Update - To_AA_Word_11() Code #更新 - To_AA_Word_11() 代码

public AA_Words_100 To_AA_Word_100()
{
    var W = new AA_Words_100();
    W.Word = Word;
    W.Word_Clean = Word.Replace("'", "");
    W.PhraseCount = PhraseCount;
    W.Clicks = Clicks;
    W.Impressions = Impressions;
    W.SalesAmt = SalesAmt;
    W.SalesOrders = SalesOrders;
    W.SalesUnits = SalesUnits;
    W.Spend = Spend;
    W.Campaign = Campaign;
    return W;
}

        

As stated in comments - Entity Framework (not sure about Entity Framework Core) by default calls DetectChanges on every Add .如评论中所述 - 实体框架(不确定实体框架核心)默认情况下在每个Add上调用DetectChanges This function, among other things, scans all entities already tracked by a context to detect changes in and between them.除其他外,此功能会扫描上下文已跟踪的所有实体,以检测它们内部和之间的变化。 That means time complexity of this function is O(n), where n is number of entities already tracked by a context.这意味着该函数的时间复杂度为 O(n),其中 n 是上下文已经跟踪的实体数。 When you do a lot of adds in a loop - time complexity becomes O(n^2), where n is total number of items added.当您在循环中进行大量添加时 - 时间复杂度变为 O(n^2),其中 n 是添加的项目总数。 So even with tiny numbers like 3000 rows, perfomance drops down very significatly.因此,即使是像 3000 行这样的小数字,性能也会显着下降。

To fix this (arguable design) issue there are couple of options:要解决这个(有争议的设计)问题,有几个选项:

  1. set AutoDetectChangesEnabled of context to false .将上下文的 AutoDetectChangesEnabled 设置为false Then manually call DetectChanges before SaveChanges .然后在SaveChanges之前手动调用DetectChanges

  2. or use AddRange instead of adding entities one by one, it calls DetectChanges just once.或者使用AddRange而不是一个一个地添加实体,它只调用一次DetectChanges

Another notes:另一个注意事项:

  1. Try to avoid reusing context between operations.尽量避免在操作之间重用上下文。 You said there was already 3000 entities tracked by context before you called first Add.你说在你调用第一个 Add 之前,已经有 3000 个实体被上下文跟踪。 It's better to create new context every time you need it, do the stuff, then dispose it.最好在每次需要时创建新的上下文,做这些事情,然后处理它。 Perfomance impact is negligible (and connections are managed by connection pool and are not necessary open or close every time you create\\dispose a context), but you will have much less problems like this one (reusing context can bite not only in the scenario you have now, but in several others).性能影响可以忽略不计(并且连接由连接池管理,每次创建/处理上下文时都不需要打开或关闭),但是像这样的问题会少得多(重用上下文不仅会在你的场景中咬人)现在有,但在其他几个)。

  2. Use AsNoTracking queries if you do not intend to modify entities returned by specific query (or if you intend to modify some of them later by attaching to context).如果您不打算修改特定查询返回的实体(或者如果您打算稍后通过附加到上下文来修改其中一些实体),请使用AsNoTracking查询。 Then context will not track them which will reduce the possibility of mentioned and other perfomance problems.然后上下文将不会跟踪它们,这将减少提到的和其他性能问题的可能性。

As for Linq To Sql - it has a similar concept of "detect changes", but it is automatically called only before commiting changes to database, not on every add, so you do not see the same problem there.至于 Linq To Sql - 它具有类似的“检测更改”概念,但仅在将更改提交到数据库之前自动调用,而不是在每次添加时自动调用,因此您不会在那里看到相同的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM