Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
A tranzakciók lehetővé teszik több adatbázisművelet atomi módon történő feldolgozását. Ha a tranzakció véglegesítése megtörtént, a rendszer az összes műveletet sikeresen alkalmazza az adatbázisra. Ha a tranzakció vissza lesz állítva, a rendszer egyik műveletet sem alkalmazza az adatbázisra.
Jótanács
A cikk mintáját a GitHubon tekintheti meg.
Alapértelmezett tranzakciós viselkedés
Alapértelmezés szerint, ha az adatbázis-szolgáltató támogatja a tranzakciókat, a rendszer egyetlen hívás SaveChanges összes módosítását alkalmazza egy tranzakcióban. Ha bármelyik módosítás meghiúsul, a rendszer visszaállítja a tranzakciót, és egyik módosítás sem lesz alkalmazva az adatbázisra. Ez azt jelenti, hogy SaveChanges a rendszer garantáltan teljesen sikeres lesz, vagy hiba esetén nem módosítja az adatbázist.
A legtöbb alkalmazás esetében ez az alapértelmezett viselkedés elegendő. Csak akkor kell manuálisan vezérelnie a tranzakciókat, ha az alkalmazás követelményei szükségesnek tartják.
Tranzakciók szabályozása
Az DbContext.Database API-val megkezdheti, véglegesítheti és visszaállíthatja a tranzakciókat. Az alábbi példa két SaveChanges műveletet mutat be, és egy LINQ-lekérdezést hajt végre egyetlen tranzakcióban:
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
}
Bár minden relációs adatbázis-szolgáltató támogatja a tranzakciókat, más szolgáltatótípusok a tranzakciós API-k meghívásakor hibát dobhatnak, vagy előfordulhat, hogy más módon jelzik a hibát, mint például a no-op.
Megjegyzés:
A tranzakciók manuális vezérlése ily módon nem kompatibilis az implicit módon meghívott újrapróbálkozási végrehajtási stratégiákkal. További információt a Kapcsolat rugalmassága című témakörben talál.
Mentési pontok
Amikor SaveChanges meghívja a rendszer, és egy tranzakció már folyamatban van a környezetben, az EF automatikusan létrehoz egy mentési pontot az adatok mentése előtt. A mentési pontok egy adatbázis-tranzakcióban azok a pontok, amelyekhez később vissza lehet térni, ha hiba történik, vagy bármilyen más okból. Ha SaveChanges bármilyen hiba lép fel, automatikusan visszaállítja a tranzakciót a mentési pontra, így a tranzakció ugyanabban az állapotban marad, mintha soha nem kezdődött volna el. Ez lehetővé teszi a problémák kijavítása és a mentés újrapróbálkozása, különösen optimista egyidejűségi problémák esetén.
Figyelmeztetés
A mentési pontok nem kompatibilisek az SQL Server több aktív eredményhalmazával (MARS). A mentési pontokat az EF nem hozza létre, ha a MARS engedélyezve van a kapcsolaton, még akkor sem, ha a MARS nincs aktívan használatban. Ha hiba történik a SaveChanges során, előfordulhat, hogy a tranzakció ismeretlen állapotban marad.
A mentési pontok manuális kezelése is lehetséges, ugyanúgy, mint a tranzakciók esetén. Az alábbi példa létrehoz egy mentési pontot egy tranzakción belül, és hiba esetén visszaállítja azt:
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
}
Kontextusközi tranzakció
A tranzakciókat több kontextuson is megoszthatja. Ez a funkció csak relációsadatbázis-szolgáltató használata esetén érhető el, mert a DbTransaction relációs adatbázisokhoz tartozó és DbConnectionazokhoz kapcsolódó használatot igényel.
Egy tranzakció megosztásához a környezeteknek mind a DbConnection, mind a DbTransaction elemet meg kell osztaniuk.
Engedélyezze a kapcsolat külső biztosítását
A DbConnection megosztásához szükség van arra, hogy a létrehozása közben egy kapcsolatot be tudjunk adni egy környezetbe.
A legegyszerűbb módja annak, hogy DbConnection külsőleg legyen megadva, ha nem használja a metódust a DbContext.OnConfiguring környezet konfigurálására, és külsőleg hozza létre DbContextOptions és továbbítja azokat a környezetkonstruktornak.
Jótanács
DbContextOptionsBuilder az API, amelyet a DbContext.OnConfiguring használta a környezet konfigurálásához; most külsőleg fogja használni a DbContextOptions létrehozására.
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
}
public DbSet<Blog> Blogs { get; set; }
}
Egy másik lehetőség, hogy továbbra is használja DbContext.OnConfiguring, de fogadjon el egy mentett DbConnection, amelyet aztán DbContext.OnConfiguring-ben használnak.
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);
}
}
Kapcsolat és tranzakció megosztása
Mostantól több olyan környezeti példányokat is létrehozhat, amelyek ugyanazon a kapcsolaton osztoznak. Ezután használja az DbContext.Database.UseTransaction(DbTransaction) API-t, hogy mindkét környezet szerepeljen ugyanabban a tranzakcióban.
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
}
Külső DbTransactions használata (csak relációs adatbázisok)
Ha több adatelérési technológiát használ egy relációs adatbázis eléréséhez, érdemes lehet megosztania egy tranzakciót a különböző technológiák által végrehajtott műveletek között.
Az alábbi példa bemutatja, hogyan hajthat végre egy ADO.NET SqlClient-műveletet és egy Entity Framework Core-műveletet ugyanabban a tranzakcióban.
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
}
A System.Transactions használata
A környezeti tranzakciók akkor használhatók, ha nagyobb hatókörön belül kell koordinálnia.
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
}
}
Lehetőség van kifejezetten tranzakcióba bejelentkezni is.
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
}
}
Megjegyzés:
Ha aszinkron API-kat használ, mindenképpen adja meg a TransactionScopeAsyncFlowOption.Enabled értéket a TransactionScope konstruktorban, hogy a környezeti tranzakció áthaladjon az aszinkron hívásokon.
A TransactionScope és a környezeti tranzakciókról további információt ebben a dokumentációban talál.
A System.Transactions korlátozásai
Az EF Core az adatbázis-szolgáltatókra támaszkodik a System.Transactions támogatásának implementálásához. Ha egy szolgáltató nem valósítja meg a System.Transactions támogatását, lehetséges, hogy ezekre az API-kra irányuló hívások teljesen figyelmen kívül lesznek hagyva. Az SqlClient támogatja.
Fontos
Javasoljuk, hogy tesztelje, hogy az API megfelelően viselkedik-e a szolgáltatóval, mielőtt a tranzakciók kezelésére támaszkodik. Ha nem, forduljon az adatbázis-szolgáltató fenntartóhoz.
A System.Transactions elosztott tranzakciótámogatása csak Windows esetén lett hozzáadva a .NET 7.0-hoz. Az elosztott tranzakciók régebbi .NET-verziókon vagy nem Windows-platformokon való használatára tett kísérletek sikertelenek lesznek.
A TransactionScope nem támogatja az aszinkron véglegesítést/visszaállítást; ez azt jelenti, hogy a törlés szinkron módon blokkolja a végrehajtó szálat, amíg a művelet be nem fejeződik.