什么时候应该在创build1000个Entity Framework对象时调用SaveChanges()? (如在import时)

我运行的每次运行都会有1000个logging。 只是在我的假设上寻找一些确认:

哪一个最有意义:

  1. 运行SaveChanges()每个AddToClassName()调用。
  2. nAddToClassName()调用运行SaveChanges()
  3. 所有AddToClassName()调用之后运行SaveChanges()

第一个选项可能是慢的吧? 由于需要分析内存中的EF对象,生成SQL等

我假设第二个选项是两个世界中最好的,因为我们可以围绕SaveChanges()调用进行try catch,并且一次只丢失n个logging,如果其中一个失败的话。 也许将每个批次存储在List <>中。 如果SaveChanges()调用成功,则删除列表。 如果失败,请logging项目。

最后一个选项可能最终会很慢,因为每个EF对象都必须在内存中,直到调用SaveChanges() 。 如果保存失败,什么都不会发生,对吧?

我会先testing一下。 性能不一定非那么糟糕。

如果您需要在一个事务中input所有行,请在所有AddToClassName类之后调用它。 如果行可以独立input,则在每行之后保存更改。 数据库一致性很重要。

第二个选项我不喜欢。 如果我从系统中导入数据(从最终用户的angular度来看),我会感到困惑,因为1是不好的,它会从1000个中减less10个数据行。 您可以尝试导入10,如果失败,请逐一尝试,然后login。

testing是否需要很长时间。 不要写“propably”。 你还不知道。 只有当它实际上是一个问题,想想其他的解决scheme(marc_s)。

编辑

我做了一些testing(以毫秒为单位):

10000行:

1行后的SaveChanges():18510,534
100行之后的SaveChanges():4350,3075
10000行之后的SaveChanges():5233,0635

50000行:

SaveChanges()在1行之后:78496,929
500行之后的SaveChanges():22302,2835
50000行后的SaveChanges():24022,8765

所以在n行之后提交的速度实际上要快得多。

我的build议是:

  • SaveChanges()在n行之后。
  • 如果一次提交失败,请逐个尝试找出错误的行。

testing类:

表:

 CREATE TABLE [dbo].[TestTable]( [ID] [int] IDENTITY(1,1) NOT NULL, [SomeInt] [int] NOT NULL, [SomeVarchar] [varchar](100) NOT NULL, [SomeOtherVarchar] [varchar](50) NOT NULL, [SomeOtherInt] [int] NULL, CONSTRAINT [PkTestTable] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] 

类:

 public class TestController : Controller { // // GET: /Test/ private readonly Random _rng = new Random(); private const string _chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private string RandomString(int size) { var randomSize = _rng.Next(size); char[] buffer = new char[randomSize]; for (int i = 0; i < randomSize; i++) { buffer[i] = _chars[_rng.Next(_chars.Length)]; } return new string(buffer); } public ActionResult EFPerformance() { string result = ""; TruncateTable(); result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(10000, 1).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 100 rows:" + EFPerformanceTest(10000, 100).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 10000 rows:" + EFPerformanceTest(10000, 10000).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 1 row:" + EFPerformanceTest(50000, 1).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 500 rows:" + EFPerformanceTest(50000, 500).TotalMilliseconds + "<br/>"; TruncateTable(); result = result + "SaveChanges() after 50000 rows:" + EFPerformanceTest(50000, 50000).TotalMilliseconds + "<br/>"; TruncateTable(); return Content(result); } private void TruncateTable() { using (var context = new CamelTrapEntities()) { var connection = ((EntityConnection)context.Connection).StoreConnection; connection.Open(); var command = connection.CreateCommand(); command.CommandText = @"TRUNCATE TABLE TestTable"; command.ExecuteNonQuery(); } } private TimeSpan EFPerformanceTest(int noOfRows, int commitAfterRows) { var startDate = DateTime.Now; using (var context = new CamelTrapEntities()) { for (int i = 1; i <= noOfRows; ++i) { var testItem = new TestTable(); testItem.SomeVarchar = RandomString(100); testItem.SomeOtherVarchar = RandomString(50); testItem.SomeInt = _rng.Next(10000); testItem.SomeOtherInt = _rng.Next(200000); context.AddToTestTable(testItem); if (i % commitAfterRows == 0) context.SaveChanges(); } } var endDate = DateTime.Now; return endDate.Subtract(startDate); } } 

我只是在我自己的代码中优化了一个非常类似的问题,并想指出一个适用于我的优化。

我发现在处理SaveChanges的大部分时间,无论是一次处理100或1000条logging,都是CPU绑定的。 所以,通过用生产者/消费者模式处理上下文(用BlockingCollection实现),我能够更好地使用CPU核心,并且从每秒总共4000次更改(由SaveChanges的返回值报告)到超过14000次/秒。 CPU利用率从大约13%(我有8个核心)转移到大约60%。 即使使用多个消费者线程,我几乎不用(非常快)磁盘IO系统,SQL Server的CPU利用率也不高于15%。

通过将保存卸载到多个线程,您可以调整提交之前的logging数和执行提交操作的线程数。

我发现创build1个生产者线程和(CPU核心数量)-1个消费者线程允许我调整每批提交的logging数,使得BlockingCollection中的项数在0和1之间波动(消费者线程花费一个项目)。 这样一来,消费线程才能够最佳地工作。

这个场景当然需要为每个批次创build一个新的上下文,即使在我的用例的单线程场景中,我发现它也更快。

如果您需要导入数千条logging,那么我会使用像SqlBulkCopy这样的东西,而不是entity framework。

  • SqlBulkCopy上的MSDN文档
  • 使用SqlBulkCopy将数据从客户端快速加载到SQL Server
  • 使用SqlBulkCopy传输数据

使用存储过程。

  1. 在Sql Server中创build一个用户定义的数据types。
  2. 在代码中创build并填充这种types的数组(非常快)。
  3. 通过一个调用将数组传递给存储过程(非常快)。

我相信这将是最简单快捷的方法。

对不起,我知道这个线程是旧的,但我认为这可以帮助其他人解决这个问题。

我有同样的问题,但有可能在您提交之前validation更改。 我的代码看起来像这样,它工作正常。 随着chUser.LastUpdated我检查,如果这是一个新的条目或只有一个变化。 因为无法重新加载不在数据库中的条目。

 // Validate Changes var invalidChanges = _userDatabase.GetValidationErrors(); foreach (var ch in invalidChanges) { // Delete invalid User or Change var chUser = (db_User) ch.Entry.Entity; if (chUser.LastUpdated == null) { // Invalid, new User _userDatabase.db_User.Remove(chUser); Console.WriteLine("!Failed to create User: " + chUser.ContactUniqKey); } else { // Invalid Change of an Entry _userDatabase.Entry(chUser).Reload(); Console.WriteLine("!Failed to update User: " + chUser.ContactUniqKey); } } _userDatabase.SaveChanges();