訓練
使用交易
交易可讓系統以不可部分完成的方式處理數個資料庫作業。 如果認可交易,就會對資料庫成功套用所有作業。 如果復原交易,則不會對資料庫套用任何作業。
提示
您可以檢視本文中的 GitHut 範例。
根據預設,如果資料庫提供者支援交易,就會在交易中套用對 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 時,其他提供者類型可能會擲回或無作業。
注意
以這種方式手動控制交易與隱含叫用的重試執行策略不相容。 如需詳細資訊,請參閱 連線復原 功能。
叫用 時 SaveChanges
,且內容上的交易已在進行中,EF 會在儲存任何資料之前自動建立 儲存點 。 儲存點是資料庫交易內的點,如果發生錯誤,或基於任何其他原因,稍後可能會回復至此點。 如果 SaveChanges
發生任何錯誤,它會自動將交易復原回儲存點,使交易處於與從未啟動相同的狀態。 這可讓您修正問題並重試儲存,特別是在發生開放式並行 問題時 。
警告
儲存點與 SQL Server 的多個作用中結果集 (MARS) 不相容。 在連線上啟用 MARS 時,EF 不會建立儲存點,即使 MARS 未主動使用也一樣。 如果在 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<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);
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();
context2.Blogs.Add(new Blog { Url = "http://dot.net" });
context2.SaveChanges();
}
// 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<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 (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<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 (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 (Exception)
{
// TODO: Handle failure
}
}
注意
如果您使用非同步 API,請務必在建構函式中 TransactionScope
指定 TransactionScopeAsyncFlowOption.Enabled ,以確保環境交易在非同步呼叫之間流動。
如需和環境交易的詳細資訊 TransactionScope
, 請參閱本檔 。
EF Core 需倚賴資料庫提供者實作對 System.Transactions 的支援。 如果提供者未實作對 System.Transactions 的支援,則對這些 API 發出的呼叫將可能完全被忽略。 SqlClient 支援它。
重要
建議您先測試該 API 是否可與您的提供者正確搭配運作,再倚賴它來管理交易。 如果無法正確搭配運作,建議您與資料庫提供者的維護人員連絡。
System.Transactions 中的分散式交易支援僅新增至適用于 Windows 的 .NET 7.0。 任何嘗試在舊版 .NET 或非 Windows 平臺上使用分散式交易都會失敗。
TransactionScope 不支援非同步認可/回復;這表示處置它會同步封鎖執行執行緒,直到作業完成為止。