什么时候应该在创build1000个Entity Framework对象时调用SaveChanges()? (如在import时)
我运行的每次运行都会有1000个logging。 只是在我的假设上寻找一些确认:
哪一个最有意义:
- 运行
SaveChanges()
每个AddToClassName()
调用。 - 每n次
AddToClassName()
调用运行SaveChanges()
。 - 在所有的
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传输数据
使用存储过程。
- 在Sql Server中创build一个用户定义的数据types。
- 在代码中创build并填充这种types的数组(非常快)。
- 通过一个调用将数组传递给存储过程(非常快)。
我相信这将是最简单快捷的方法。
对不起,我知道这个线程是旧的,但我认为这可以帮助其他人解决这个问题。
我有同样的问题,但有可能在您提交之前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();