Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Transakce umožňují zpracování několika databázových operací atomovým způsobem. Pokud je transakce potvrzena, všechny operace jsou úspěšně použity na databázi. Pokud je transakce vrácena zpět, žádná z operací se nepoužije na databázi.
Návod
Ukázkové tohoto článku si můžete prohlédnout na GitHubu.
Výchozí chování transakcí
Pokud poskytovatel databáze ve výchozím nastavení podporuje transakce, všechny změny v jednom volání SaveChanges se použijí v transakci. Pokud některé ze změn selžou, transakce se vrátí zpět a žádná z těchto změn se nepoužije v databázi. To znamená, že je zaručeno, že SaveChanges buď zcela uspěje, nebo zanechá databázi neupravenou, pokud dojde k chybě.
U většiny aplikací je toto výchozí chování dostatečné. Transakce byste měli řídit pouze ručně, pokud to požadavky vaší aplikace budou považovat za nezbytné.
Řízení transakcí
K zahájení, potvrzení a vrácení transakcí zpět můžete použít rozhraní API DbContext.Database. Následující příklad ukazuje dvě operace SaveChanges a dotaz LINQ, který se spouští v jedné transakci:
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
}
Zatímco všichni poskytovatelé relačních databází podporují transakce, ostatní typy poskytovatelů mohou vyvolat chybu nebo reagovat no-op při volání transakčních rozhraní API.
Poznámka:
Ruční řízení transakcí tímto způsobem není kompatibilní s implicitně vyvolanými strategiemi opakovaného provádění. Další informace najdete v části odolnosti připojení.
Savepoints
Při vyvolání SaveChanges a transakce již probíhá v kontextu, EF automaticky vytvoří savepoint před uložením dat. Body uložení jsou body v rámci databázové transakce, která se může později vrátit zpět, pokud dojde k chybě nebo z jakéhokoli jiného důvodu. Pokud SaveChanges dojde k nějaké chybě, automaticky vrátí transakci zpět do bodu uložení, takže transakce zůstane ve stejném stavu, jako kdyby se nikdy nezačala. To vám umožní možná opravit problémy a znovu se pokusit o uložení, zejména když dojde k problémům s optimistickou souběžností.
Varování
Body ukládání nejsou kompatibilní s několika aktivními sadami výsledků (MARS) SQL Serveru. Savepoints se nevytvoří EF, pokud je na připojení povolená MARS, i když se MARS aktivně nepoužívá. Pokud dojde k chybě během SaveChanges, transakce může být ponechána v neznámém stavu.
Je také možné ručně spravovat body ukládání, stejně jako u transakcí. Následující příklad vytvoří bod uložení v rámci transakce a vrátí se k němu při selhání:
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
}
Transakce napříč kontexty
Transakci můžete také sdílet napříč několika kontextovými instancemi. Tato funkce je k dispozici pouze při použití zprostředkovatele relační databáze, protože vyžaduje použití DbTransaction a DbConnection, které jsou specifické pro relační databáze.
Chcete-li sdílet transakci, kontexty musí sdílet DbConnection i DbTransaction.
Povolit externí poskytnutí připojení
Sdílení DbConnection vyžaduje schopnost předat připojení do kontextu během jeho vytváření.
Nejjednodušším způsobem, jak povolit externí poskytnutí DbConnection, je přestat používat metodu DbContext.OnConfiguring ke konfiguraci kontextu a externě vytvořit DbContextOptions a předat je konstruktoru kontextu.
Návod
DbContextOptionsBuilder je rozhraní API, které jste použili v DbContext.OnConfiguring ke konfiguraci kontextu, teď ho použijete externě k vytvoření DbContextOptions.
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
}
public DbSet<Blog> Blogs { get; set; }
}
Alternativou je pokračovat v používání DbContext.OnConfiguring, ale přijmout DbConnection, který je uložen a následně použit v 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);
}
}
Sdílet připojení a transakci
Teď můžete vytvořit více kontextových instancí, které sdílejí stejné připojení. Pak pomocí rozhraní API DbContext.Database.UseTransaction(DbTransaction) zapište oba kontexty do stejné transakce.
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
}
Použití externích dbTransactions (pouze relační databáze)
Pokud pro přístup k relační databázi používáte více technologií přístupu k datům, můžete chtít sdílet transakci mezi operacemi prováděnými těmito různými technologiemi.
Následující příklad ukazuje, jak provést operaci ADO.NET SqlClient a operaci Entity Framework Core ve stejné transakci.
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
}
Použití System.Transactions
Je možné použít ambientní transakce, pokud potřebujete koordinovat napříč větším rozsahem.
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
}
}
Je také možné se zapsat do explicitní transakce.
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
}
}
Poznámka:
Pokud používáte asynchronní rozhraní API, nezapomeňte zadat TransactionScopeAsyncFlowOption.Enabled v konstruktoru TransactionScope, aby se zajistilo, že kontext transakce probíhá přes asynchronní volání.
Další informace o TransactionScope a neaktivních transakcích naleznete v této dokumentaci.
Omezení systémových transakcí
EF Core využívá poskytovatele databází k implementaci podpory pro System.Transactions. Pokud poskytovatel neimplementuje podporu pro System.Transactions, je možné, že volání těchto rozhraní API budou zcela ignorována. SqlClient ho podporuje.
Důležité
Před tím, než na něj spoléháte při správě transakcí, doporučujeme otestovat, že se rozhraní API chová správně s vaším poskytovatelem. Doporučujeme kontaktovat správce poskytovatele databáze, pokud to tak není.
Podpora distribuovaných transakcí v system.Transactions byla přidána pouze do .NET 7.0 pro Windows. Všechny pokusy o použití distribuovaných transakcí ve starších verzích .NET nebo na platformách jiných než Windows selžou.
TransactionScope nepodporuje asynchronní potvrzení nebo vrácení zpět; to znamená, že jeho synchronní odstranění blokuje spuštěné vlákno, dokud nebude operace dokončena.