Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Транзакции позволяют обрабатывать несколько операций базы данных атомарным образом. Если транзакция зафиксирована, все операции успешно применяются к базе данных. Если транзакция откатится, ни одна из операций не применяется к базе данных.
Подсказка
Вы можете скачать используемый в этой статье пример из репозитория GitHub.
Поведение транзакций по умолчанию
По умолчанию, если поставщик базы данных поддерживает транзакции, все изменения в одном вызове SaveChanges
применяются в транзакции. Если какой-либо из изменений завершится сбоем, транзакция откатится, и к базе данных не применяются никакие изменения. Это означает, что SaveChanges
гарантировано либо полностью успешно завершится, либо оставит базу данных неизмененной, если возникнет ошибка.
Для большинства приложений это поведение по умолчанию достаточно. Вы должны управлять транзакциями вручную, только если это необходимо по требованиям вашего приложения.
Управление транзакциями
Вы можете использовать API DbContext.Database
для начала, фиксации и отката транзакций. В следующем примере показаны две SaveChanges
операции и запрос LINQ, выполняемый в одной транзакции:
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
}
Хотя все поставщики реляционных баз данных поддерживают транзакции, другие типы поставщиков могут выдавать исключения или ошибки, такие как no-op, при вызове API транзакций.
Замечание
Управление транзакциями вручную несовместимо с неявным вызовом стратегий повторных попыток. Дополнительные сведения см. в разделе "Устойчивость подключений ".
Точки сохранения
Когда SaveChanges
вызывается и транзакция уже выполняется в контексте, EF автоматически создает точку сохранения перед сохранением данных. Точки сохранения — это точки в транзакции базы данных, к которым можно откатиться позже, если возникает ошибка или по какой-либо другой причине. Если SaveChanges
возникает любая ошибка, она автоматически откатывает транзакцию в точку сохранения, оставляя транзакцию в том же состоянии, что и если она никогда не была запущена. Это позволяет исправить проблемы и повторить сохранение, в частности, при возникновении проблем с оптимистическим параллелизмом .
Предупреждение
Точки сохранения несовместимы с несколькими активными результирующими наборами SQL Server (MARS). Точки сохранения не будут созданы с использованием Entity Framework, если в подключении включен MARS, даже если MARS активно не используется. Если во время SaveChanges возникает ошибка, транзакция может оставаться в неизвестном состоянии.
Кроме того, можно вручную управлять точками сохранения, так же как и с транзакциями. В следующем примере создается точка сохранения в транзакции и выполняется откат к ней при сбое:
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
}
Транзакция межконтекстная
Вы также можете совместно использовать транзакцию в нескольких экземплярах контекста. Эта функция доступна только при использовании поставщика реляционной базы данных, так как требуется использовать DbTransaction
и DbConnection
, которые относятся к реляционным базам данных.
Чтобы поделиться транзакцией, контексты должны совместно использовать DbConnection
и DbTransaction
.
Разрешить внешний доступ к подключению
DbConnection
Для совместного использования требуется возможность передачи соединения в контекст при его создании.
Самый простой способ разрешить внешнее предоставление DbConnection
— перестать использовать метод DbContext.OnConfiguring
для настройки контекста и создавать DbContextOptions
внешне, передавая их конструктору контекста.
Подсказка
DbContextOptionsBuilder
— это API, используемый для DbContext.OnConfiguring
настройки контекста, теперь вы собираетесь использовать его внешне для создания DbContextOptions
.
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
}
public DbSet<Blog> Blogs { get; set; }
}
Альтернативой является продолжение использования DbContext.OnConfiguring
, но принятие DbConnection
, который сохраняется, а затем используется в 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);
}
}
Предоставление общего доступа к подключению и транзакциям
Теперь можно создать несколько экземпляров контекста, которые совместно используют одно и то же соединение. Затем используйте DbContext.Database.UseTransaction(DbTransaction)
API для включения обоих контекстов в одну транзакцию.
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
}
Использование внешних dbTransactions (только реляционных баз данных)
При использовании нескольких технологий доступа к данным для доступа к реляционной базе данных может потребоваться предоставить доступ к транзакции между операциями, выполняемыми этими разными технологиями.
В следующем примере показано, как выполнить операцию sqlClient ADO.NET и операцию Entity Framework Core в той же транзакции.
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
}
Использование System.Transactions
Можно использовать внешние транзакции, если требуется координировать работу в большей области.
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
}
}
Кроме того, можно участвовать в явной транзакции.
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
}
}
Замечание
Если вы используете асинхронные API, обязательно укажите TransactionScopeAsyncFlowOption.Enabled в конструкторе TransactionScope
, чтобы обеспечить потоки внешних транзакций через асинхронные вызовы.
Дополнительные сведения о TransactionScope
транзакциях и контекстуальных транзакциях см. в этой документации.
Ограничения System.Transactions
EF Core использует поставщики баз данных для реализации поддержки System.Transactions. Если поставщик не реализует поддержку System.Transactions, возможно, что вызовы этих API будут полностью игнорироваться. SqlClient поддерживает его.
Это важно
Рекомендуется проверить правильность работы API с поставщиком, прежде чем полагаться на него для управления транзакциями. Рекомендуется обратиться к администратору поставщика базы данных, если это не так.
Поддержка распределенных транзакций в System.Transactions добавлена только в .NET 7.0 для Windows. Любая попытка использовать распределенные транзакции на более старых версиях .NET или на платформах, отличных от Windows, завершится ошибкой.
TransactionScope не поддерживает асинхронную фиксацию или откат; это означает, что удаление его синхронно блокирует исполняемый поток до завершения операции.