使用事务Using Transactions

事务允许以原子方式处理多个数据库操作。 如果已提交事务,则所有操作都会成功应用到数据库。 如果已回滚事务,则所有操作都不会应用到数据库。

提示

可在 GitHub 上查看此文章的示例

默认事务行为Default transaction behavior

默认情况下,如果数据库提供程序支持事务,则会在单次调用 SaveChanges() 时将所有更改都将应用到事务中。 如果其中有任何更改失败,则会回滚事务且所有更改都不会应用到数据库。 这意味着,SaveChanges() 可保证要么完全成功,要么在出现错误时不修改数据库。

对于大多数应用程序,此默认行为已足够。 除非应用程序确有需求,否则不应手动控制事务。

控制事务Controlling transactions

可以使用 DbContext.Database API 开始、提交和回滚事务。 以下示例显示了在单个事务中执行的两个 SaveChanges() 操作以及 一个LINQ 查询。

并非所有数据库提供程序都支持事务。 调用事务 API 时,某些提供程序可能会引发异常或不执行任何操作。

  1. using (var context = new BloggingContext())
  2. {
  3. using (var transaction = context.Database.BeginTransaction())
  4. {
  5. try
  6. {
  7. context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
  8. context.SaveChanges();
  9. context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
  10. context.SaveChanges();
  11. var blogs = context.Blogs
  12. .OrderBy(b => b.Url)
  13. .ToList();
  14. // Commit transaction if all commands succeed, transaction will auto-rollback
  15. // when disposed if either commands fails
  16. transaction.Commit();
  17. }
  18. catch (Exception)
  19. {
  20. // TODO: Handle failure
  21. }
  22. }
  23. }

跨上下文事务(仅限关系数据库)Cross-context transaction (relational databases only)

您还可以跨多个上下文实例共享一个事务。 此功能仅在使用关系数据库提供程序时才可用,因为它需要使用特定于关系数据库的 DbTransactionDbConnection

若要共享事务,上下文必须共享 DbConnectionDbTransaction

允许在外部提供连接Allow connection to be externally provided

共享 DbConnection 需要在构造上下文时向其中传入连接的功能。

允许在外部提供 DbConnection 的最简单方式是,停止使用 DbContext.OnConfiguring 方法来配置上下文并在外部创建 DbContextOptions,然后将其传递到上下文构造函数。

提示

DbContextOptionsBuilder 是在 DbContext.OnConfiguring 中用于配置上下文的 API,现在即将在外部使用它来创建 DbContextOptions

  1. public class BloggingContext : DbContext
  2. {
  3. public BloggingContext(DbContextOptions<BloggingContext> options)
  4. : base(options)
  5. { }
  6. public DbSet<Blog> Blogs { get; set; }
  7. }

替代方法是继续使用 DbContext.OnConfiguring,但接受已保存并随后在 DbContext.OnConfiguring 中使用的 DbConnection

  1. public class BloggingContext : DbContext
  2. {
  3. private DbConnection _connection;
  4. public BloggingContext(DbConnection connection)
  5. {
  6. _connection = connection;
  7. }
  8. public DbSet<Blog> Blogs { get; set; }
  9. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  10. {
  11. optionsBuilder.UseSqlServer(_connection);
  12. }
  13. }

共享连接和事务Share connection and transaction

现在可以创建共享同一连接的多个上下文实例。 然后使用 DbContext.Database.UseTransaction(DbTransaction) API 在同一事务中登记两个上下文。

  1. var options = new DbContextOptionsBuilder<BloggingContext>()
  2. .UseSqlServer(new SqlConnection(connectionString))
  3. .Options;
  4. using (var context1 = new BloggingContext(options))
  5. {
  6. using (var transaction = context1.Database.BeginTransaction())
  7. {
  8. try
  9. {
  10. context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
  11. context1.SaveChanges();
  12. using (var context2 = new BloggingContext(options))
  13. {
  14. context2.Database.UseTransaction(transaction.GetDbTransaction());
  15. var blogs = context2.Blogs
  16. .OrderBy(b => b.Url)
  17. .ToList();
  18. }
  19. // Commit transaction if all commands succeed, transaction will auto-rollback
  20. // when disposed if either commands fails
  21. transaction.Commit();
  22. }
  23. catch (Exception)
  24. {
  25. // TODO: Handle failure
  26. }
  27. }
  28. }

使用外部 DbTransactions(仅限关系数据库)Using external DbTransactions (relational databases only)

如果使用多个数据访问技术来访问关系数据库,则可能希望在这些不同技术所执行的操作之间共享事务。

以下示例显示了如何在同一事务中执行 ADO.NET SqlClient 操作和 Entity Framework Core 操作。

  1. using (var connection = new SqlConnection(connectionString))
  2. {
  3. connection.Open();
  4. using (var transaction = connection.BeginTransaction())
  5. {
  6. try
  7. {
  8. // Run raw ADO.NET command in the transaction
  9. var command = connection.CreateCommand();
  10. command.Transaction = transaction;
  11. command.CommandText = "DELETE FROM dbo.Blogs";
  12. command.ExecuteNonQuery();
  13. // Run an EF Core command in the transaction
  14. var options = new DbContextOptionsBuilder<BloggingContext>()
  15. .UseSqlServer(connection)
  16. .Options;
  17. using (var context = new BloggingContext(options))
  18. {
  19. context.Database.UseTransaction(transaction);
  20. context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
  21. context.SaveChanges();
  22. }
  23. // Commit transaction if all commands succeed, transaction will auto-rollback
  24. // when disposed if either commands fails
  25. transaction.Commit();
  26. }
  27. catch (System.Exception)
  28. {
  29. // TODO: Handle failure
  30. }
  31. }
  32. }

使用 System.TransactionsUsing System.Transactions

备注

此功能是 EF Core 2.1 中的新增功能。

如果需要跨较大作用域进行协调,则可以使用环境事务。

  1. using (var scope = new TransactionScope(
  2. TransactionScopeOption.Required,
  3. new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
  4. {
  5. using (var connection = new SqlConnection(connectionString))
  6. {
  7. connection.Open();
  8. try
  9. {
  10. // Run raw ADO.NET command in the transaction
  11. var command = connection.CreateCommand();
  12. command.CommandText = "DELETE FROM dbo.Blogs";
  13. command.ExecuteNonQuery();
  14. // Run an EF Core command in the transaction
  15. var options = new DbContextOptionsBuilder<BloggingContext>()
  16. .UseSqlServer(connection)
  17. .Options;
  18. using (var context = new BloggingContext(options))
  19. {
  20. context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
  21. context.SaveChanges();
  22. }
  23. // Commit transaction if all commands succeed, transaction will auto-rollback
  24. // when disposed if either commands fails
  25. scope.Complete();
  26. }
  27. catch (System.Exception)
  28. {
  29. // TODO: Handle failure
  30. }
  31. }
  32. }

还可以在显式事务中登记。

  1. using (var transaction = new CommittableTransaction(
  2. new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
  3. {
  4. var connection = new SqlConnection(connectionString);
  5. try
  6. {
  7. var options = new DbContextOptionsBuilder<BloggingContext>()
  8. .UseSqlServer(connection)
  9. .Options;
  10. using (var context = new BloggingContext(options))
  11. {
  12. context.Database.OpenConnection();
  13. context.Database.EnlistTransaction(transaction);
  14. // Run raw ADO.NET command in the transaction
  15. var command = connection.CreateCommand();
  16. command.CommandText = "DELETE FROM dbo.Blogs";
  17. command.ExecuteNonQuery();
  18. // Run an EF Core command in the transaction
  19. context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
  20. context.SaveChanges();
  21. context.Database.CloseConnection();
  22. }
  23. // Commit transaction if all commands succeed, transaction will auto-rollback
  24. // when disposed if either commands fails
  25. transaction.Commit();
  26. }
  27. catch (System.Exception)
  28. {
  29. // TODO: Handle failure
  30. }
  31. }

System.Transactions 的限制Limitations of System.Transactions

  1. EF Core 依赖数据库提供程序以实现对 System.Transactions 的支持。 虽然支持在 .NET Framework 的 ADO.NET 提供程序之间十分常见,但最近才将 API 添加到 .NET Core,因此支持并未得到广泛应用。 如果提供程序未实现对 System.Transactions 的支持,则可能会完全忽略对这些 API 的调用。 SqlClient for .NET Core 从 2.1 及以上版本开始支持 System.Transactions。 如果你尝试使用此功能,SqlClient for .NET Core 2.0 会抛出异常。

    重要

    建议你测试在依赖提供程序以管理事务之前 API 与该提供程序的行为是否正确。 如果不正确,则建议你与数据库提供程序的维护人员联系。

  2. 自版本 2.1 起,.NET Core 中的 System.Transactions 实现不包括对分布式事务的支持,因此不能使用 TransactionScopeCommittableTransaction 来跨多个资源管理器协调事务。