Entity Framework(6)中单个和多个SaveChanges()调用的事务有什么区别?

6
我希望了解在相同的数据库上下文中执行事务时,以下3种方式的实际差异是什么:
1)使用一个单独的SaveChanges()进行多个操作,而不显式地使用SQL事务。
using (TestDbContext db = new TestDbContext())
{
    // first operation
    // second operation
    db.SaveChanges();
}

2) 使用SQL事务,在一次SaveChanges()中执行多个操作

using (TestDbContext db = new TestDbContext())
using (DbContextTransaction trans = db.Database.BeginTransaction())
{
     // operation 1
     // operation 2
     db.SaveChanges();    
     trans.commit();
}

3) 使用sql事务进行多个操作并且有多个SaveChanges()

using (TestDbContext db = new TestDbContext())
using (DbContextTransaction trans = db.BeginTransaction())
{
     // operation 1
     db.SaveChanges();    
     // operation 2
     db.SaveChanges();

     trans.commit();
}

在(2)和(3)中,如果commit()应该实际上执行请求的sql查询到数据库,那么它是否真的不同,例如每个操作保存更改还是一次性保存所有操作的更改?
如果(1)也可以安全地在同一个数据库上下文中执行多个操作,那么手动启动事务的主要用途是什么?我想我们可以手动提供try / catch块来回滚事务,如果发生了一些不好的事情,但据我所知,SaveChanges()也至少在SQLServer中自动覆盖它。
**更新:另一件事是:我应该将db上下文和事务变量设置为类级别,还是这些变量应该仅限于包含方法的局部变量?
2个回答

5
如果您不启动事务,它是隐式的。这意味着,在调用后,您执行的所有SaveChanges()操作将立即在数据库中可用。
如果您启动事务,SaveChanges()仍然执行更新,但数据在调用commit之前对其他连接不可用。
您可以通过设置断点、创建新对象、将其添加到上下文并执行SaveChanges()来测试此功能。您会发现,在该调用之后,ID属性将具有一个值,但在事务上执行提交之前,数据库中没有相应的行。
至于您的第二个问题,这实际上取决于并发需求、您的类正在执行的操作以及您正在处理的数据量。它不是一个范围问题,而是一个代码执行问题。
上下文不是线程安全的,因此只要在应用程序中只有一个线程访问上下文,您就可以将其放在更广泛的范围内。但是,如果应用程序的其他实例正在访问数据,则必须确保刷新数据以获取最新模型。您还应考虑加载到内存中的模型越多,保存所需的时间就越长。
我倾向于在执行要执行的操作附近创建上下文,并尽快将其处置。

当然,这是显而易见的,但我的意思是如果实际执行事务会有什么区别。当然,在情况(2)和(3)中,直到调用commit()才会执行事务。 - tab87vn
我不确定我理解你试图理解什么。但是,如果您没有指定事务,则仍然存在一个事务。它基本上包装在EF在服务器上创建的每个SQL语句周围。 - MutantNinjaCodeMonkey
确切地说,你说得对,“如果你不指定一个事务,仍然会存在一个事务”。然而,根据我的观察,无论是隐式还是显式触发事务,都没有实际差别,我的意思是,它不会影响开发的灵活性、系统性能或其他方面。例如,如果我自己开始事务并提交更改(记录日志、基准测试等),那么有没有什么我不能在调用SaveChanges()自动完成一切时做到的呢? - tab87vn
1
默认行为是“一刀切”,所以对于你的情况,可能是正确的。但是有些情况下,你需要一个事务(例如在可能执行或不执行更新的模型上进行行锁定,或者你需要提前知道某个ID,而不是通过EF进行关系管理,但在提交所有行之前)。或者,您可能正在通过共同的事务混合EF操作和非EF操作。此外,PaulG下面的答案阐述了一个很好的观点。 - MutantNinjaCodeMonkey
1
它们几乎是相同的。在示例1中,隐式事务是在 SaveChanges() 创建的。EF不会为查询创建事务,因此在此之前的任何读取操作都不包括在事务中。在示例2中,您在操作开始时创建它,因此根据隔离级别设置,通过读取它们可能更早地锁定行。 - MutantNinjaCodeMonkey
显示剩余4条评论

2
你的问题似乎与实体框架无关,更多地涉及sql事务。Sql事务是一次单独的“原子”更改。也就是说,要么所有更改都被提交,要么没有一个被提交。
你没有提供覆盖该场景的示例,但如果您添加了另一个示例,例如:
using (TestDbContext db = new TestDbContext())
{
     // operation 1
     db.SaveChanges();    
     // operation 2
     db.SaveChanges();
}

在这个例子中,如果您的第一次操作成功保存,但第二次操作失败,那么您可能会面临数据在第一步提交时就已经无效的情况。因此,您可以使用 SQL 事务来将两个 SaveChanges 包装成一个单独的操作,这意味着要么全部提交数据,要么全部不提交

嗨,问题是关于在EF中使用事务。我认为DbContext和DbContextTransaction都是专属于EF的,对吗? 我给出的代码片段可能代表了一个典型的情况,在该情况下对多个数据库表进行了几个更改(假设创建一个新订单涉及插入订单记录和插入订单详细信息记录)。正如你所说,只有当(1)和(2)都被执行或者都没有执行时,才会执行创建订单的事务。 - tab87vn
我清楚地看到你的例子和我的例子之间的区别,并知道事务在这里的重要性,即原子性。然而,我并不清楚手动操作事务的意义,而不是在出现问题时显式触发回滚(使用try/catch)。根据这篇文章https://msdn.microsoft.com/en-us/data/dn456843.aspx,显式使用EF的DbContextTransaction允许在同一上下文中执行多个操作,但即使在我的示例(1)中,似乎我们可以执行任意数量的操作,直到调用SaveChanges()。 - tab87vn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接