Použití transakcí
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.
Tip
Ukázku pro tento článek najdete 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ď je databáze zcela úspěšná, nebo nechat databázi nezměněnou, 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 vaše požadavky na aplikaci považují za nezbytné.
Řízení transakcí
Rozhraní API můžete použít DbContext.Database
k zahájení, potvrzení a vrácení transakcí zpět. Následující příklad ukazuje dvě SaveChanges
operace a dotaz LINQ, který se spouští v jedné transakci:
using var context = new BloggingContext();
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 nebo bez operace při zavolání rozhraní API transakcí.
Poznámka
Ruční řízení transakcí tímto způsobem není kompatibilní s implicitně vyvolanými strategiemi provádění. Další informace najdete v tématu Připojení o odolnosti.
Savepoints
Při SaveChanges
vyvolání a transakce již probíhá v kontextu, EF automaticky vytvoří bod uložení 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 savepointu, takže transakce zůstane ve stejném stavu, jako kdyby se nikdy nezačala. To vám umožní opravit problémy a opakovat ukládání, zejména v případě, že dojde k problémům s optimistickou souběžností .
Upozorňující
Body ukládání nejsou kompatibilní s několika aktivními sadami výsledků (MARS) SQL Serveru. Funkce Savepoints se nevytvoří ef, pokud je u připojení povolená služba 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();
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 jak a DbConnection
a DbTransaction
.
Povolit externí poskytnutí připojení
DbConnection
Sdílení vyžaduje možnost předat připojení do kontextu při jeho vytváření.
Nejjednodušší způsob, jak umožnit DbConnection
externí poskytnutí, je zastavit použití DbContext.OnConfiguring
metody ke konfiguraci kontextu a externě vytvořit DbContextOptions
a předat je do konstruktoru kontextu.
Tip
DbContextOptionsBuilder
je rozhraní API, které DbContext.OnConfiguring
jste použili ke konfiguraci kontextu, a 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 pak 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ílení připojení a transakce
Teď můžete vytvořit více kontextových instancí, které sdílejí stejné připojení. Pak použijte DbContext.Database.UseTransaction(DbTransaction)
rozhraní API k zařazení obou kontextů do stejné transakce.
using var connection = new SqlConnection(connectionString);
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using var context1 = new BloggingContext(options);
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();
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 okolí 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é zahrnout 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 v konstruktoru TransactionScope
zadat TransactionScopeAsyncFlowOption.Enabled, aby se zajistilo, že okolí transakce proudí přes asynchronní volání.
Další informace o TransactionScope
a okolí transakcí naleznete v této dokumentaci.
Omezení System.Transactions
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 ne.
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.