事務允許以原子方式處理多個資料庫操作。 當交易被認可時,所有作業都會成功套用到資料庫。 如果交易回滾,則不會將任何操作套用至資料庫。
小提示
您可以在 GitHub 上檢視本文 範例。
預設交易行為
根據預設,如果資料庫提供者支援交易,則單一呼叫 SaveChanges
中的所有變更都會套用在交易中。 如果有任何變更失敗,則會回復交易,且不會將任何變更套用至資料庫。 這表示 SaveChanges
保證會完全成功,或在發生錯誤時讓資料庫保持未修改。
對於大部分的應用程式而言,此預設行為就已足夠。 只有在應用程式需求認為有必要時,才應該手動控制交易。
控制交易
您可以使用 DbContext.Database
API 來開始、認可和回復交易。 下列範例顯示兩個 SaveChanges
作業,以及單一交易中執行的LINQ查詢:
using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();
try
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
await context.SaveChangesAsync();
var blogs = await context.Blogs
.OrderBy(b => b.Url)
.ToListAsync();
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
await transaction.CommitAsync();
}
catch (Exception)
{
// TODO: Handle failure
}
雖然所有關係型資料庫提供者都支援交易,但呼叫交易 API 時,其他類型的提供者可能會擲出錯誤或返回 no-op。
備註
以這種方式手動控制交易與隱含叫用的重試執行策略不相容。 如需詳細資訊,請參閱 連線復原。
儲存點
當叫用 SaveChanges
且在上下文中已經進行交易時,EF 會在儲存任何資料之前,自動建立 儲存點。 儲存點是資料庫交易內的點,如果發生錯誤,或基於任何其他原因,稍後可能會回復至此點。 如果 SaveChanges
發生任何錯誤,它會自動將交易回復至儲存點,使交易處於與從未啟動相同的狀態。 這可讓您修正問題並重試儲存,特別是在發生樂觀並行控制問題時。
警告
儲存點與 SQL Server 的多個作用中結果集 (MARS) 不相容。 在連線上啟用MARS時,EF不會建立儲存點,即使MARS未主動使用也一樣。 如果在 SaveChanges 期間發生錯誤,交易可能會處於未知狀態。
您也可以手動管理儲存點,就如同交易一樣。 以下範例在交易中建立一個儲存點,若發生錯誤則回滾至該儲存點:
using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();
try
{
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/dotnet/" });
await context.SaveChangesAsync();
await transaction.CreateSavepointAsync("BeforeMoreBlogs");
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/visualstudio/" });
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/aspnet/" });
await context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch (Exception)
{
// If a failure occurred, we rollback to the savepoint and can continue the transaction
await transaction.RollbackToSavepointAsync("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<BloggingContext> options)
: base(options)
{
}
public DbSet<Blog> Blogs { get; set; }
}
替代方法是繼續使用 DbContext.OnConfiguring
,但接受儲存的 DbConnection
,然後在 DbContext.OnConfiguring
中使用 。
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);
}
}
共用連線和交易
您現在可以建立多個共用相同連線的內容實例。 然後使用 DbContext.Database.UseTransaction(DbTransaction)
API,將兩個上下文登記到相同的交易中。
using var connection = new SqlConnection(connectionString);
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using var context1 = new BloggingContext(options);
await using var transaction = await context1.Database.BeginTransactionAsync();
try
{
context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context1.SaveChangesAsync();
using (var context2 = new BloggingContext(options))
{
await context2.Database.UseTransactionAsync(transaction.GetDbTransaction());
var blogs = await context2.Blogs
.OrderBy(b => b.Url)
.ToListAsync();
context2.Blogs.Add(new Blog { Url = "http://dot.net" });
await context2.SaveChangesAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
await transaction.CommitAsync();
}
catch (Exception)
{
// TODO: Handle failure
}
使用外部 DbTransactions (僅限關係資料庫)
如果您使用多個數據存取技術來存取關係資料庫,您可能會想要在這些不同技術所執行的作業之間共用交易。
下列範例示範如何在相同的交易中執行 ADO.NET SqlClient 作業和 Entity Framework Core 作業。
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
await using var transaction = (SqlTransaction)await connection.BeginTransactionAsync();
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))
{
await context.Database.UseTransactionAsync(transaction);
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
await transaction.CommitAsync();
}
catch (Exception)
{
// TODO: Handle failure
}
使用 System.Transactions
如果您需要在更大範圍內協調,則可以使用環境式交易。
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
try
{
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM dbo.Blogs";
await command.ExecuteNonQueryAsync();
// 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" });
await context.SaveChangesAsync();
}
// 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<BloggingContext>()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
await context.Database.OpenConnectionAsync();
context.Database.EnlistTransaction(transaction);
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM dbo.Blogs";
await command.ExecuteNonQueryAsync();
// Run an EF Core command in the transaction
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
await context.Database.CloseConnectionAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
}
如需 TransactionScope
和環境交易的詳細資訊,請參閱本檔。
System.Transactions 的限制
EF Core 依賴資料庫提供者來實作 System.Transactions 的支援。 如果提供者未實作 System.Transactions 的支援,可能會完全忽略對這些 API 的呼叫。 SqlClient 支援它。
這很重要
建議您先測試 API 是否與提供者正確運作,再依賴它來管理交易。 如果資料庫提供者未這樣做,建議您連絡資料庫提供者的維護者。
System.Transactions 中的分散式交易支援僅新增至適用於 Windows 的 .NET 7.0。 任何嘗試在舊版 .NET 或非 Windows 平臺上使用分散式交易都會失敗。
TransactionScope 不支持異步提交/回滾;這表示同步處置它會封鎖執行緒,直到操作完成為止。