c# - Entity Framework 批量插入/更新关系

标签 c# sql-server database performance entity-framework

我有一个场景,我需要对大量数据执行数据库更新。有一些外键关系需要同时添加,我得到了一个外来对象列表,所以我不必每次都访问数据库来检查它们是否存在/添加它们等:

using(DbEntities db = new DbEntities())
{
   // Get list of all books so don't have to hit every time
   Dictionary<int, Book> books = db.Books.ToDictionary(k => k.BookId, v => v);

   // Loop through big file to import each row
   foreach(var item in bigFile)
   {
      Person person = new Person { FirstName = item.FirstName, LastName = item.LastName };

      foreach(var book in item.Books)
      {
         if(!books.ContainsKey(book.BookId))
         {
            // Add book to DB if doesn't exist
            Book bookToAdd = new Book { BookId = book.BookId, Name = book.Name };
            db.Books.Add(bookToAdd);

            books.Add(bookToAdd.BookId, bookToAdd);
         }

         person.Books.Add(books[book.BookId]);
      }

      db.People.Add(person);
   }

   db.SaveChanges();
}

此解决方案的问题是导入开始时很快,然后随着它的进行而减慢,变得非常慢。这似乎是由于上下文因更改跟踪而变得臃肿。

我看到帖子建议使用 db.Configuration.AutoDetectChangesEnabled = false 但是当我这样做时,关系不会被添加。我可以通过强制 DetectChanges() 来完成这项工作,但这似乎违背了目的,因为我必须在循环的每次迭代中都这样做。

因此,我将 DB 上下文移动到循环中,以便每次都重新创建它。这样做意味着我不能再拥有分离的书籍列表,所以我必须为每一行对数据库进行 .Any().Single() 调用(我不知道这是否是一个主要的性能问题,但总是尽量不频繁地访问数据库):

// Loop through big file to import each row
foreach(var item in bigFile)
{
   // Create DB context each time
   using(DbEntities db = new DbEntities())
   {
      Person person = new Person { FirstName = item.FirstName, LastName = item.LastName };

      foreach(var book in item.Books)
      {
         if(!db.Books.Any(m => m.BookId = bookId))
         {
            // Add book to DB if doesn't exist
            Book bookToAdd = new Book { BookId = bookId, Name = book.Name

            db.Books.Add(bookToAdd);
         }

         person.Books.Add(db.Books.Single(m => m.BookId = bookId));
      }

      db.People.Add(person);

      db.SaveChanges();
   }
}

这大大加快了它的速度,但在大约 5,000-10,000 行之后它仍然开始变慢,我想知道我的选择是什么? ...除了使用存储过程来完成这一切!

最佳答案

IMO 这两种解决方案都不好。第一个是在内存(和数据库上下文)中加载整个现有的 Books 表,第二个对每本书执行 2 个数据库查询 - 一个使用 Any ,另一个使用 单例

由于我的测试没有显示上下文更改跟踪的性能问题,我将使用第一种方法的变体和第二种方法的元素。我不会加载整个 Books 表,而是使用按需填充的本地字典,对每个新书 ID 进行单个数据库查询:

using (DbEntities db = new DbEntities())
{
    // The local book dictionary
    Dictionary<int, Book> books = new Dictionary<int, Book>();

    // Loop through big file to import each row
    foreach (var item in bigFile)
    {
        Person person = new Person { FirstName = item.FirstName, LastName = item.LastName };

        foreach (var itemBook in item.Books)
        {
            Book book;

            // Try get from local dictionary
            if (!books.TryGetValue(itemBook.BookId, out book))
            {
                // Try get from db
                book = db.Books.FirstOrDefault(e => e.BookId == itemBook.BookId);
                if (book == null)
                {
                    // Add book to DB if doesn't exist
                    book = new Book { BookId = itemBook.BookId, Name = itemBook.Name };
                    db.Books.Add(book);
                }
                // add to local dictionary
                books.Add(book.BookId, book);
            }

            person.Books.Add(book);
        }

        db.People.Add(person);
    }

    db.SaveChanges();
}

关于c# - Entity Framework 批量插入/更新关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43252161/

相关文章:

c# - 一般执法

sql-server - SQL Server 系统管理员能否在数据库中创建规则以阻止用户执行某些类型的查询?

sql - 自定义处理以在 SQL 中将行转换为列

database - 我应该将计算值与变量一起存储在我的数据库中吗?

c# - 使用 C# 填充 bootstrap 下拉列表

c# - 用于查询事件网络和关联连接的 WMI

sql-server - SSMS : "The SELECT permission was denied on the object ' extended_properties', 数据库 'mssqlsystem_resource' 中的权限问题,...错误 229)"

sql - 找出SQL Server中的调用存储过程

c# - 指定用于 WSHttpBinding 客户端连接的 SSL 协议(protocol)版本

mysql - 中值计算从 MySQL 到 SQL Server 的转换