Megosztás:


Tranzakciók használata

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

  1. 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.

  2. 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.

  3. 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.