Como usar transaçõesUsing Transactions

As transações permitem que várias operações de banco de dados sejam processadas de forma atômica.Transactions allow several database operations to be processed in an atomic manner. Se a transação for confirmada, todas as operações serão aplicadas com êxito ao banco de dados.If the transaction is committed, all of the operations are successfully applied to the database. Se a transação for revertida, nenhuma operação será aplicadas ao banco de dados.If the transaction is rolled back, none of the operations are applied to the database.

Dica

Veja o exemplo deste artigo no GitHub.You can view this article's sample on GitHub.

Comportamento de transação padrãoDefault transaction behavior

Por padrão, se o provedor de banco de dados oferecer suporte a transações, todas as alterações em uma única chamada para SaveChanges() serão aplicadas em uma transação.By default, if the database provider supports transactions, all changes in a single call to SaveChanges() are applied in a transaction. Se qualquer uma das alterações falhar, a transação é revertida e nenhuma das alterações será aplicada ao banco de dados.If any of the changes fail, then the transaction is rolled back and none of the changes are applied to the database. Isso significa que é garantido que o SaveChanges() terá êxito ou sairá do banco de dados sem modificação caso ocorra algum erro.This means that SaveChanges() is guaranteed to either completely succeed, or leave the database unmodified if an error occurs.

Para a maioria dos aplicativos, esse comportamento padrão é suficiente.For most applications, this default behavior is sufficient. Você só deve controlar as transações manualmente se os requisitos do aplicativo considerarem necessário.You should only manually control transactions if your application requirements deem it necessary.

Como controlar transaçõesControlling transactions

Você pode usar a API do DbContext.Database para iniciar, confirmar e reverter transações.You can use the DbContext.Database API to begin, commit, and rollback transactions. O exemplo a seguir mostra duas operações do SaveChanges() e uma consulta LINQ sendo executada em uma única transação.The following example shows two SaveChanges() operations and a LINQ query being executed in a single transaction.

Nem todos os provedores de banco de dados oferecem suporte a transações.Not all database providers support transactions. Alguns provedores podem gerar ou ficar não operacional quando as APIs de transação forem chamadas.Some providers may throw or no-op when transaction APIs are called.

        using (var context = new BloggingContext())
        {
            using (var transaction = context.Database.BeginTransaction())
            {
                try
                {
                    context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
                    context.SaveChanges();

                    context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
                    context.SaveChanges();

                    var blogs = context.Blogs
                        .OrderBy(b => b.Url)
                        .ToList();

                    // Commit transaction if all commands succeed, transaction will auto-rollback
                    // when disposed if either commands fails
                    transaction.Commit();
                }
                catch (Exception)
                {
                    // TODO: Handle failure
                }
            }
        }

Transação entre diferentes contextos (somente bancos de dados relacionais)Cross-context transaction (relational databases only)

Você também pode compartilhar uma transação em várias instâncias de contexto.You can also share a transaction across multiple context instances. Esta funcionalidade só está disponível ao usar um provedor de banco de dados relacional, porque ele requer o uso do DbTransaction e DbConnection, que são específicos para bancos de dados relacionais.This functionality is only available when using a relational database provider because it requires the use of DbTransaction and DbConnection, which are specific to relational databases.

Para compartilhar uma transação, os contextos devem compartilhar um DbConnection e um DbTransaction.To share a transaction, the contexts must share both a DbConnection and a DbTransaction.

Permitir que a conexão seja fornecido externamenteAllow connection to be externally provided

Compartilhar um DbConnection requer a capacidade de aprovar uma conexão em um contexto ao construí-la.Sharing a DbConnection requires the ability to pass a connection into a context when constructing it.

A maneira mais fácil de permitir que o DbConnection seja fornecido externamente é parar de usar o método DbContext.OnConfiguring para configurar o contexto e criar externamente DbContextOptions e aprová-los para o construtor de contexto.The easiest way to allow DbConnection to be externally provided, is to stop using the DbContext.OnConfiguring method to configure the context and externally create DbContextOptions and pass them to the context constructor.

Dica

DbContextOptionsBuilder é a API usada no DbContext.OnConfiguring para configurar o contexto e agora você irá usá-la externamente para criar DbContextOptions.DbContextOptionsBuilder is the API you used in DbContext.OnConfiguring to configure the context, you are now going to use it externally to create DbContextOptions.

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { }

    public DbSet<Blog> Blogs { get; set; }
}

Uma alternativa é continuar usando o DbContext.OnConfiguring, mas aceitar um DbConnection que seja salvo e, depois, usado no DbContext.OnConfiguring.An alternative is to keep using DbContext.OnConfiguring, but accept a DbConnection that is saved and then used in 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 uma conexão e transaçãoShare connection and transaction

Agora você pode criar várias instâncias de contexto que compartilham a mesma conexão.You can now create multiple context instances that share the same connection. Em seguida, use a API do DbContext.Database.UseTransaction(DbTransaction) para inscrever os dois contextos na mesma transação.Then use the DbContext.Database.UseTransaction(DbTransaction) API to enlist both contexts in the same transaction.

var options = new DbContextOptionsBuilder<BloggingContext>()
    .UseSqlServer(new SqlConnection(connectionString))
    .Options;

using (var context1 = new BloggingContext(options))
{
    using (var transaction = context1.Database.BeginTransaction())
    {
        try
        {
            context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context1.SaveChanges();

            using (var context2 = new BloggingContext(options))
            {
                context2.Database.UseTransaction(transaction.GetDbTransaction());

                var blogs = context2.Blogs
                    .OrderBy(b => b.Url)
                    .ToList();
            }

            // Commit transaction if all commands succeed, transaction will auto-rollback
            // when disposed if either commands fails
            transaction.Commit();
        }
        catch (Exception)
        {
            // TODO: Handle failure
        }
    }
}

Usando DbTransactions externos (somente bancos de dados relacionais)Using external DbTransactions (relational databases only)

Se você estiver usando várias tecnologias de acesso a dados para acessar um banco de dados relacional, é possível compartilhar uma transação entre operações executadas por essas diferentes tecnologias.If you are using multiple data access technologies to access a relational database, you may want to share a transaction between operations performed by these different technologies.

O exemplo a seguir mostra como executar uma operação do SqlClient do ADO.NET e uma operação do Entity Framework Core na mesma transação.The following example, shows how to perform an ADO.NET SqlClient operation and an Entity Framework Core operation in the same transaction.

var connection = new SqlConnection(connectionString);
connection.Open();

using (var transaction = connection.BeginTransaction())
{
    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))
        {
            context.Database.UseTransaction(transaction);
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context.SaveChanges();
        }

        // Commit transaction if all commands succeed, transaction will auto-rollback
        // when disposed if either commands fails
        transaction.Commit();
    }
    catch (System.Exception)
    {
        // TODO: Handle failure
    }
}

Como usar System.TransactionsUsing System.Transactions

Observação

Este recurso é novo no EF Core 2.1.This feature is new in EF Core 2.1.

É possível usar transações ambientes se você precisar coordenar um escopo mais amplo.It is possible to use ambient transactions if you need to coordinate across a larger scope.

using (var scope = new TransactionScope(
    TransactionScopeOption.Required, 
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    var connection = new SqlConnection(connectionString);
    connection.Open();

    try
    {
        // Run raw ADO.NET command in the transaction
        var command = connection.CreateCommand();
        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))
        {
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context.SaveChanges();
        }

        // Commit transaction if all commands succeed, transaction will auto-rollback
        // when disposed if either commands fails
        scope.Complete();
    }
    catch (System.Exception)
    {
        // TODO: Handle failure
    }
}

Também é possível se inscrever em uma transação explícita.It is also possible to enlist in an explicit transaction.

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))
        {
            context.Database.OpenConnection();
            context.Database.EnlistTransaction(transaction);
      
            // Run raw ADO.NET command in the transaction
            var command = connection.CreateCommand();
            command.CommandText = "DELETE FROM dbo.Blogs";
            command.ExecuteNonQuery();

            // Run an EF Core command in the transaction
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context.SaveChanges();
            context.Database.CloseConnection();
        }

        // Commit transaction if all commands succeed, transaction will auto-rollback
        // when disposed if either commands fails
        transaction.Commit();
    }
    catch (System.Exception)
    {
        // TODO: Handle failure
    }
}

Limitações do System.TransactionsLimitations of System.Transactions

  1. O EF Core depende dos provedores de banco de dados para implementar o suporte para System.Transactions.EF Core relies on database providers to implement support for System.Transactions. Embora o suporte seja muito comum entre os provedores de ADO.NET para .NET Framework, a API só foi adicionada recentemente ao .NET Core e, portanto, o suporte ainda não está tão difundido.Although support is quite common among ADO.NET providers for .NET Framework, the API has only been recently added to .NET Core and hence support is not be as widespread. Se um provedor não implementar o suporte para System.Transactions, é possível que as chamadas para essas APIs sejam ignoradas completamente.If a provider does not implement support for System.Transactions, it is possible that calls to these APIs will be completely ignored. O SqlClient para .NET Core não oferecerá suporte do 2.1 em diante.SqlClient for .NET Core does support it from 2.1 onwards. O SqlClient para .NET Core 2.0 gerará uma exceção se você tentar usar o recurso.SqlClient for .NET Core 2.0 will throw an exception of you attempt to use the feature.

    Importante

    Recomendamos testar se a API funciona corretamente com seu provedor antes de depender dela para o gerenciamento de transações.It is recommended that you test that the API behaves correctly with your provider before you rely on it for managing transactions. Caso ela não funcione, fale com o mantenedor do provedor do banco de dados.You are encouraged to contact the maintainer of the database provider if it does not.

  2. A partir da versão 2.1, a implementação do System.Transactions no .NET Core não incluirá o suporte a transações distribuídas, portanto, você não pode usar TransactionScope ou CommitableTransaction para coordenar transações em vários gerenciadores de recursos.As of version 2.1, the System.Transactions implementation in .NET Core does not include support for distributed transactions, therefore you cannot use TransactionScope or CommitableTransaction to coordinate transactions across multiple resource managers.