Транзакции в EF Core C#

Entity Framework Core предоставляет гибкие механизмы для управления транзакциями, обеспечивая атомарность и согласованность данных при выполнении нескольких операций.

Поведение транзакции по умолчанию

Если поставщик базы данных поддерживает транзакции, то при вызове SaveChanges все изменения применяются в одной транзакции. В случае ошибки все изменения откатываются, предотвращая частичное сохранение данных.

using var context = new AppDbContext();
var product = new Product { Name = "Laptop", Price = 1200 };

context.Products.Add(product);
context.SaveChanges();

Явное управление транзакциями

Для явного управления транзакциями можно использовать методы BeginTransaction, Commit и Rollback.

using var context = new AppDbContext();
using var transaction = context.Database.BeginTransaction();

try
{
    var product = new Product { Name = "Tablet", Price = 800 };
    context.Products.Add(product);
    context.SaveChanges();

    transaction.Commit();
}
catch
{
    transaction.Rollback();
}

Выполнение SQL-запросов в транзакции

Можно выполнять SQL-запросы внутри транзакции с помощью ExecuteSqlRaw.

using var context = new AppDbContext();
using var transaction = context.Database.BeginTransaction();

try
{
    context.Database.ExecuteSqlRaw("INSERT INTO Products (Name, Price) VALUES ('Smartphone', 500)");
    context.Database.ExecuteSqlRaw("UPDATE Products SET Price = 550 WHERE Name = 'Smartphone'");
    transaction.Commit();
}
catch
{
    transaction.Rollback();
}

Использование точек сохранения

Entity Framework Core автоматически создает точку сохранения перед вызовом SaveChanges. Можно также управлять точками сохранения вручную.

using var context = new AppDbContext();
using var transaction = context.Database.BeginTransaction();

try
{
    context.Products.Add(new Product { Name = "Mouse", Price = 50 });
    context.SaveChanges();

    transaction.CreateSavepoint("BeforeMoreProducts");

    context.Products.Add(new Product { Name = "Keyboard", Price = 100 });
    context.Products.Add(new Product { Name = "Headphones", Price = 150 });
    context.SaveChanges();

    transaction.Commit();
}
catch
{
    transaction.RollbackToSavepoint("BeforeMoreProducts");
}

Использование транзакции в нескольких контекстах

Для использования одной транзакции в нескольких экземплярах DbContext нужно передавать общее подключение и транзакцию.

using var connection = new SqlConnection(connectionString);
var options = new DbContextOptionsBuilder<AppDbContext>()
    .UseSqlServer(connection)
    .Options;

using var context1 = new AppDbContext(options);
using var transaction = await context1.Database.BeginTransactionAsync();
try
{
    context1.Products.Add(new Product { Name = "Camera", Price = 1200 });
    await context1.SaveChangesAsync();

    using (var context2 = new AppDbContext(options))
    {
        await context2.Database.UseTransactionAsync(transaction.GetDbTransaction());

        var products = await context2.Products.ToListAsync();
        context2.Products.Add(new Product { Name = "Tripod", Price = 200 });
        await context2.SaveChangesAsync();
    }

    await transaction.CommitAsync();
}
catch
{
    transaction.Rollback();
}

Использование TransactionScope

TransactionScope позволяет автоматически управлять транзакцией без явного вызова Commit или Rollback.

using System.Transactions;

using var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
using var context = new AppDbContext();

var product = new Product { Name = "Monitor", Price = 300 };
context.Products.Add(product);
context.SaveChanges();

scope.Complete();

Использование внешних транзакций базы данных

Entity Framework Core поддерживает внешние транзакции, например, при комбинировании ADO.NET и EF Core.

using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();

using var transaction = (SqlTransaction)await connection.BeginTransactionAsync();
try
{
    var command = connection.CreateCommand();
    command.Transaction = transaction;
    command.CommandText = "DELETE FROM Products";
    command.ExecuteNonQuery();

    var options = new DbContextOptionsBuilder<AppDbContext>()
        .UseSqlServer(connection)
        .Options;

    using (var context = new AppDbContext(options))
    {
        await context.Database.UseTransactionAsync(transaction);
        context.Products.Add(new Product { Name = "Gaming Console", Price = 500 });
        await context.SaveChangesAsync();
    }

    await transaction.CommitAsync();
}
catch
{
    transaction.Rollback();
}