Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
As transações permitem que várias operações de banco de dados sejam processadas de maneira atômica. Se a transação for confirmada, todas as operações serão aplicadas com êxito ao banco de dados. Se a transação for revertida, nenhuma das operações será aplicada ao banco de dados.
Dica
Você pode exibir o exemplo deste artigo no GitHub.
Comportamento de transação padrão
Por padrão, se o provedor de banco de dados der suporte a transações, todas as alterações em uma única chamada para SaveChanges
serão aplicadas em uma transação. Se alguma das alterações falhar, a transação será revertida e nenhuma das alterações será aplicada ao banco de dados. Isso significa que SaveChanges
é garantido a ter êxito completo, ou deixar o banco de dados inalterado se ocorrer um erro.
Para a maioria dos aplicativos, esse comportamento padrão é suficiente. Você só deve controlar as transações manualmente se os requisitos do aplicativo considerarem necessário.
Controlando transações
Você pode usar a DbContext.Database
API para iniciar, confirmar e reverter transações. O exemplo a seguir mostra duas SaveChanges
operações e uma consulta LINQ sendo executadas em uma única transação:
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
}
Embora todos os provedores de banco de dados relacionais ofereçam suporte a transações, outros tipos de provedores podem gerar ou no-op quando as APIs de transação são chamadas.
Observação
O controle manual de transações dessa forma é incompatível com estratégias de execução com repetição invocadas implicitamente. Consulte Resiliência de Conexão para obter mais informações.
Pontos de salvamento
Quando SaveChanges
é invocado e uma transação já está em andamento no contexto, o EF cria automaticamente um ponto de salvamento antes de salvar os dados. Savepoints são pontos dentro de uma transação de banco de dados aos quais pode-se reverter subsequentemente, se ocorrer um erro ou por qualquer outro motivo. Se SaveChanges
encontrar algum erro, ele reverterá automaticamente a transação para o ponto de salvamento, deixando a transação no mesmo estado como se nunca tivesse sido iniciada. Isso permite que você corrija problemas e tente salvar novamente, especialmente quando ocorrem problemas de concorrência otimista.
Aviso
Os pontos de salvamento são incompatíveis com OS MARS (Vários Conjuntos de Resultados Ativos) do SQL Server. Os pontos de salvamento não serão criados pelo EF quando o MARS estiver habilitado na conexão, mesmo que o MARS não esteja ativamente em uso. Se ocorrer um erro durante SaveChanges, a transação poderá ser deixada em um estado desconhecido.
Também é possível gerenciar manualmente os pontos de salvamento, assim como é com transações. O exemplo a seguir cria um ponto de salvamento em uma transação e reverte para ele em caso de falha:
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
}
Transação entre contextos
Você também pode compartilhar uma transação em várias instâncias de contexto. Essa funcionalidade só está disponível ao usar um provedor de banco de dados relacional porque requer o uso de DbTransaction
e DbConnection
, que são específicos para bancos de dados relacionais.
Para compartilhar uma transação, os contextos devem compartilhar um DbConnection
e um DbTransaction
.
Permitir que a conexão seja fornecida externamente
Compartilhar um DbConnection
requer a capacidade de passar uma conexão para um contexto ao construí-lo.
A maneira mais fácil de permitir que DbConnection
seja fornecido externamente é parar de usar o método DbContext.OnConfiguring
para configurar o contexto, criar DbContextOptions
externamente e passá-los para o construtor de contexto.
Dica
DbContextOptionsBuilder
é a API em DbContext.OnConfiguring
que você usou para configurar o contexto, agora você vai usá-lo externamente para criar DbContextOptions
.
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
}
public DbSet<Blog> Blogs { get; set; }
}
Uma alternativa é continuar usando DbContext.OnConfiguring
, mas aceitar um DbConnection
que seja salvo e, em seguida, usado em 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);
}
}
Compartilhar conexão e transação
Agora você pode criar várias instâncias de contexto que compartilham a mesma conexão. Em seguida, use a DbContext.Database.UseTransaction(DbTransaction)
API para inscrever ambos os contextos na mesma transação.
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
}
Usando DbTransactions externos (somente bancos de dados relacionais)
Se você estiver usando várias tecnologias de acesso a dados para acessar um banco de dados relacional, talvez queira compartilhar uma transação entre operações executadas por essas diferentes tecnologias.
O exemplo a seguir mostra como executar uma operação do SqlClient ADO.NET e uma operação do Entity Framework Core na mesma transação.
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
}
Usando System.Transactions
É possível usar transações ambiente se você precisar coordenar em um escopo maior.
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
}
}
Também é possível se inscrever em uma transação explícita.
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
}
}
Observação
Se você estiver usando APIs assíncronas, especifique TransactionScopeAsyncFlowOption.Enabled no TransactionScope
construtor para garantir que a transação ambiente flua entre chamadas assíncronas.
Para obter mais informações sobre TransactionScope
e transações de ambiente, consulte esta documentação.
Limitações de System.Transactions
O EF Core depende de provedores de banco de dados para implementar o suporte para System.Transactions. Se um provedor não implementar o suporte para System.Transactions, é possível que as chamadas a essas APIs sejam completamente ignoradas. O SqlClient dá suporte a ele.
Importante
É recomendável que você teste se a API se comporta corretamente com seu provedor antes de depender dela para gerenciar transações. Você será incentivado a entrar em contato com o mantenedor do provedor de banco de dados se ele não o fizer.
O suporte a transações distribuídas no System.Transactions foi adicionado apenas ao .NET 7.0 para Windows. Qualquer tentativa de usar transações distribuídas em versões mais antigas do .NET ou em plataformas não Windows falhará.
O TransactionScope não dá suporte à confirmação/reversão assíncrona; isso significa que descartá-lo de forma síncrona bloqueia o thread em execução até que a operação seja concluída.