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 é garantido que o SaveChanges terá êxito ou sairá do banco de dados sem modificação caso ocorra algum 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 automáticas
Você pode controlar se o EF cria automaticamente uma transação ao chamar SaveChanges e nenhuma transação de usuário existe. Configure AutoTransactionBehavior para um dos seguintes valores:
- WhenNeeded (padrão): o EF cria uma transação somente quando necessário. Por exemplo, a maioria das instruções SQL simples são executadas em uma transação implicitamente, portanto, o EF não cria uma transação explícita.
- Always: o EF sempre cria uma transação se nenhuma transação de usuário existir. Isso pode adicionar idas e voltas ao banco de dados, o que pode prejudicar o desempenho.
- Never: o EF nunca cria uma transação automaticamente.
Por exemplo, configure o EF para sempre criar uma transação antes de chamar SaveChanges:
context.Database.AutoTransactionBehavior = AutoTransactionBehavior.Always;
Always pode ser útil se o código do aplicativo depender de os callbacks de criação de transações de IDbTransactionInterceptor serem acionados quando SaveChanges não usa uma transação de usuário.
Aviso
Use AutoTransactionBehavior.Never com cuidado. Se SaveChanges precisar executar vários comandos e ocorrer uma falha, os comandos anteriores podem já ter sido efetivados, resultando em alterações parciais no banco de dados.
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 bancos de dados relacionais ofereçam suporte a transações, outros tipos de provedores podem gerar um 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 de 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. Os pontos de salvamento são pontos dentro de uma transação de banco de dados para os quais é possível reverter posteriormente, 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 MARS (Conjunto de resultados ativos múltiplos) 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 aprovar uma conexão em um contexto ao construí-la.
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 ambientes se você precisar coordenar um escopo mais amplo.
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 oferece suporte a confirmação/reversão assíncrona, o que significa que o descarte bloqueia de forma síncrona o thread de execução até que a operação seja concluída.