事务允许以原子方式处理多个数据库操作。 如果已提交事务,则所有操作都会成功应用到数据库。 如果已回滚事务,则所有操作都不会应用到数据库。
默认事务行为
默认情况下,如果数据库提供程序支持事务,则会在事务中应用对 SaveChanges
的单一调用中的所有更改。 如果其中有任何更改失败,则会回滚事务且所有更改都不会应用到数据库。 这意味着,SaveChanges
可保证完全成功,或在出现错误时不修改数据库。
对于大多数应用程序,此默认行为已足够。 如果应用程序要求被视为有必要,则应该仅手动控制事务。
可以使用 DbContext.Database
API 开始、提交和回滚事务。 以下示例显示了在单个事务中执行的两个 SaveChanges
操作以及一个 LINQ 查询:
using var context = new BloggingContext();
using var transaction = context.Database.BeginTransaction();
try
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
context.SaveChanges();
var blogs = context.Blogs
.OrderBy(b => b.Url)
.ToList();
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
虽然所有关系数据库提供程序都支持事务,但在调用事务 API 时,可能会引发其他提供程序类型或不执行任何操作。
EF Core 5.0 中已引入此功能。
如果调用 SaveChanges
且事务已在上下文中进行,则在保存任何数据之前,EF 会自动创建保存点。 保存点是数据库事务中的点,如果发生错误或出于任何其他原因,可能会回滚到这些点。 如果 SaveChanges
遇到任何错误,则会自动将事务回滚到保存点,使事务处于相同状态,就好像从未开始。 这样可以解决问题并重试保存,尤其是在出现乐观并发问题时。
警告
保存点与 SQL Server 的多重活动结果集不兼容,因此不使用。 如果在 SaveChanges
过程中出现错误,则该事务可能处于未知状态。
还可以手动管理保存点,就像管理事务一样。 以下示例在事务中创建保存点,并在失败时回滚到该保存点:
using var context = new BloggingContext();
using var transaction = context.Database.BeginTransaction();
try
{
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/dotnet/" });
context.SaveChanges();
transaction.CreateSavepoint("BeforeMoreBlogs");
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/visualstudio/" });
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/aspnet/" });
context.SaveChanges();
transaction.Commit();
}
catch (Exception)
{
// If a failure occurred, we rollback to the savepoint and can continue the transaction
transaction.RollbackToSavepoint("BeforeMoreBlogs");
// TODO: Handle failure, possibly retry inserting blogs
}
还可以跨多个上下文实例共享一个事务。 此功能仅在使用关系数据库提供程序时才可用,因为该提供程序需要使用特定于关系数据库的 DbTransaction
和 DbConnection
。
若要共享事务,上下文必须共享 DbConnection
和 DbTransaction
。
共享 DbConnection
需要在构造上下文时向其中传入连接的功能。
允许在外部提供 DbConnection
的最简单方式是,停止使用 DbContext.OnConfiguring
方法来配置上下文并在外部创建 DbContextOptions
,然后将其传递到上下文构造函数。
提示
DbContextOptionsBuilder
是在 DbContext.OnConfiguring
中用于配置上下文的 API,现在即将在外部使用它来创建 DbContextOptions
。
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions options)
: base(options)
{
}
public DbSet Blogs { get; set; }
}
替代方法是继续使用 DbContext.OnConfiguring
,但接受已保存并随后在 DbContext.OnConfiguring
中使用的 DbConnection
。
public class BloggingContext : DbContext
{
private DbConnection _connection;
public BloggingContext(DbConnection connection)
{
_connection = connection;
}
public DbSet Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connection);
}
}
现在可以创建共享同一连接的多个上下文实例。 然后使用 DbContext.Database.UseTransaction(DbTransaction)
API 在同一事务中登记两个上下文。
using var connection = new SqlConnection(connectionString);
var options = new DbContextOptionsBuilder()
.UseSqlServer(connection)
.Options;
using var context1 = new BloggingContext(options);
using var transaction = context1.Database.BeginTransaction();
try
{
context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context1.SaveChanges();
using (var context2 = new BloggingContext(options))
{
context2.Database.UseTransaction(transaction.GetDbTransaction());
var blogs = context2.Blogs
.OrderBy(b => b.Url)
.ToList();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
如果使用多个数据访问技术来访问关系数据库,则可能希望在这些不同技术所执行的操作之间共享事务。
以下示例显示了如何在同一事务中执行 ADO.NET SqlClient 操作和 Entity Framework Core 操作。
using var connection = new SqlConnection(connectionString);
connection.Open();
using var transaction = connection.BeginTransaction();
try
{
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = "DELETE FROM dbo.Blogs";
command.ExecuteNonQuery();
// Run an EF Core command in the transaction
var options = new DbContextOptionsBuilder()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
context.Database.UseTransaction(transaction);
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
如果需要跨较大作用域进行协调,则可以使用环境事务。
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
using var connection = new SqlConnection(connectionString);
connection.Open();
try
{
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM dbo.Blogs";
command.ExecuteNonQuery();
// Run an EF Core command in the transaction
var options = new DbContextOptionsBuilder()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
scope.Complete();
}
catch (Exception)
{
// TODO: Handle failure
}
}
还可以在显式事务中登记。
using (var transaction = new CommittableTransaction(
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
var connection = new SqlConnection(connectionString);
try
{
var options = new DbContextOptionsBuilder()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
context.Database.OpenConnection();
context.Database.EnlistTransaction(transaction);
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM dbo.Blogs";
command.ExecuteNonQuery();
// Run an EF Core command in the transaction
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
context.SaveChanges();
context.Database.CloseConnection();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
}
EF Core 依赖数据库提供程序以实现对 System.Transactions 的支持。 如果提供程序未实现对 System.Transactions 的支持,则可能会完全忽略对这些 API 的调用。 SqlClient 支持它。
重要
建议你测试在依赖提供程序以管理事务之前 API 与该提供程序的行为是否正确。 如果不正确,则建议你与数据库提供程序的维护人员联系。
System.Transactions 的 .NET Core 实现当前不包括对分布式事务的支持,因此不能使用 TransactionScope
或 CommittableTransaction
来跨多个资源管理器协调事务。