使用事务Using Transactions
事务允许以原子方式处理多个数据库操作。 如果已提交事务,则所有操作都会成功应用到数据库。 如果已回滚事务,则所有操作都不会应用到数据库。
提示
可在 GitHub 上查看此文章的示例。
默认事务行为Default transaction behavior
默认情况下,如果数据库提供程序支持事务,则会在单次调用 SaveChanges()
时将所有更改都将应用到事务中。 如果其中有任何更改失败,则会回滚事务且所有更改都不会应用到数据库。 这意味着,SaveChanges()
可保证要么完全成功,要么在出现错误时不修改数据库。
对于大多数应用程序,此默认行为已足够。 除非应用程序确有需求,否则不应手动控制事务。
控制事务Controlling transactions
可以使用 DbContext.Database
API 开始、提交和回滚事务。 以下示例显示了在单个事务中执行的两个 SaveChanges()
操作以及 一个LINQ 查询。
并非所有数据库提供程序都支持事务。 调用事务 API 时,某些提供程序可能会引发异常或不执行任何操作。
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
}
}
}
跨上下文事务(仅限关系数据库)Cross-context transaction (relational databases only)
您还可以跨多个上下文实例共享一个事务。 此功能仅在使用关系数据库提供程序时才可用,因为它需要使用特定于关系数据库的 DbTransaction
和 DbConnection
。
若要共享事务,上下文必须共享 DbConnection
和 DbTransaction
。
允许在外部提供连接Allow connection to be externally provided
共享 DbConnection
需要在构造上下文时向其中传入连接的功能。
允许在外部提供 DbConnection
的最简单方式是,停止使用 DbContext.OnConfiguring
方法来配置上下文并在外部创建 DbContextOptions
,然后将其传递到上下文构造函数。
提示
DbContextOptionsBuilder
是在 DbContext.OnConfiguring
中用于配置上下文的 API,现在即将在外部使用它来创建 DbContextOptions
。
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
}
替代方法是继续使用 DbContext.OnConfiguring
,但接受已保存并随后在 DbContext.OnConfiguring
中使用的 DbConnection
。
public class BloggingContext : DbContext
{
private DbConnection _connection;
public BloggingContext(DbConnection connection)
{
_connection = connection;
}
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connection);
}
}
共享连接和事务Share connection and transaction
现在可以创建共享同一连接的多个上下文实例。 然后使用 DbContext.Database.UseTransaction(DbTransaction)
API 在同一事务中登记两个上下文。
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(new SqlConnection(connectionString))
.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
}
}
}
使用外部 DbTransactions(仅限关系数据库)Using external DbTransactions (relational databases only)
如果使用多个数据访问技术来访问关系数据库,则可能希望在这些不同技术所执行的操作之间共享事务。
以下示例显示了如何在同一事务中执行 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<BloggingContext>()
.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 (System.Exception)
{
// TODO: Handle failure
}
}
}
使用 System.TransactionsUsing System.Transactions
备注
此功能是 EF Core 2.1 中的新增功能。
如果需要跨较大作用域进行协调,则可以使用环境事务。
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<BloggingContext>()
.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 (System.Exception)
{
// TODO: Handle failure
}
}
}
还可以在显式事务中登记。
using (var transaction = new CommittableTransaction(
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
var connection = new SqlConnection(connectionString);
try
{
var options = new DbContextOptionsBuilder<BloggingContext>()
.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 (System.Exception)
{
// TODO: Handle failure
}
}
System.Transactions 的限制Limitations of System.Transactions
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.1 起,.NET Core 中的 System.Transactions 实现不包括对分布式事务的支持,因此不能使用
TransactionScope
或CommittableTransaction
来跨多个资源管理器协调事务。