Utilisation de transactionsUsing Transactions

Les transactions permettent à plusieurs opérations de base de données d’être traitées de manière atomique.Transactions allow several database operations to be processed in an atomic manner. Si la transaction est validée, toutes les opérations sont appliquées avec succès à la base de données.If the transaction is committed, all of the operations are successfully applied to the database. Si la transaction est restaurée, aucune des opérations n’est appliquée à la base de données.If the transaction is rolled back, none of the operations are applied to the database.

Conseil

Vous pouvez afficher cet exemple sur GitHub.You can view this article's sample on GitHub.

Comportement de transaction par défautDefault transaction behavior

Par défaut, si le fournisseur de base de données prend en charge les transactions, toutes les modifications dans un seul appel à SaveChanges() sont appliquées à une transaction.By default, if the database provider supports transactions, all changes in a single call to SaveChanges() are applied in a transaction. Si certaines des modifications échouent, la transaction est annulée et aucune des modifications n’est appliquée à la base de données.If any of the changes fail, then the transaction is rolled back and none of the changes are applied to the database. Cela signifie que SaveChanges() réussit complètement ou laisse la base de données non modifiée si une erreur se produit.This means that SaveChanges() is guaranteed to either completely succeed, or leave the database unmodified if an error occurs.

Pour la plupart des applications, ce comportement par défaut est suffisant.For most applications, this default behavior is sufficient. Vous devez uniquement contrôler manuellement les transactions si les exigences de votre application le jugent nécessaire.You should only manually control transactions if your application requirements deem it necessary.

Contrôle des transactionsControlling transactions

Vous pouvez utiliser l’API DbContext.Database pour commencer, valider et annuler les transactions.You can use the DbContext.Database API to begin, commit, and rollback transactions. L’exemple suivant montre deux opérations SaveChanges() et une requête LINQ en cours d’exécution dans une transaction unique.The following example shows two SaveChanges() operations and a LINQ query being executed in a single transaction.

Tous les fournisseurs de base de données ne prennent pas en charge les transactions.Not all database providers support transactions. Certains fournisseurs peuvent lever une exception ou ne pas exécuter d’opération lorsque des API de transaction sont appelées.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
        }
    }
}

Transaction à contexte croisé (bases de données relationnelles uniquement)Cross-context transaction (relational databases only)

Vous pouvez également partager une transaction sur plusieurs instances de contexte.You can also share a transaction across multiple context instances. Cette fonctionnalité est disponible uniquement lorsque vous utilisez un fournisseur de base de données relationnelle, car elle requiert l’utilisation de DbTransaction et DbConnection, qui sont propres aux bases de données relationnelles.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.

Pour partager une transaction, les contextes doivent partager une DbConnection et une DbTransaction.To share a transaction, the contexts must share both a DbConnection and a DbTransaction.

Autoriser la fourniture externe de la connexionAllow connection to be externally provided

Le partage d’une DbConnection nécessite la possibilité de passer une connexion dans un contexte lors de la construction.Sharing a DbConnection requires the ability to pass a connection into a context when constructing it.

Le moyen le plus simple pour autoriser la DbConnection à être fournie en externe, arrêtez d’utiliser la méthode DbContext.OnConfiguring pour configurer le contexte et créez les DbContextOptions en externe avant de les passer au constructeur de contexte.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.

Conseil

DbContextOptionsBuilder est l’API que vous avez utilisée dans DbContext.OnConfiguring pour configurer le contexte. Vous allez maintenant l’utiliser en externe pour créer 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; }
}

Une alternative consiste à continuer à utiliser DbContext.OnConfiguring, mais accepte une DbConnection qui est enregistrée et ensuite utilisée dans 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);
    }
}

Partage de connexions et transactionsShare connection and transaction

Vous pouvez désormais créer plusieurs instances de contexte qui partagent la même connexion.You can now create multiple context instances that share the same connection. Utilisez ensuite l’API DbContext.Database.UseTransaction(DbTransaction) pour inscrire les deux contextes dans la même transaction.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
        }
    }
}

Utilisation de DbTransactions externes (bases de données relationnelles uniquement)Using external DbTransactions (relational databases only)

Si vous utilisez plusieurs technologies d’accès aux données pour accéder à une base de données relationnelle, vous souhaiterez partager une transaction entre les opérations effectuées par ces différentes technologies.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.

L’exemple suivant montre comment effectuer une opération ADO.NET SqlClient et une opération Entity Framework Core dans la même transaction.The following example, shows how to perform an ADO.NET SqlClient operation and an Entity Framework Core operation in the same transaction.

using (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
        }
    }
}

Utilisation de System.TransactionsUsing System.Transactions

Note

Cette fonctionnalité est une nouveauté d’EF Core 2.1.This feature is new in EF Core 2.1.

Il est possible d’utiliser les transactions ambiantes si vous avez besoin de coordonner sur une plus grande portée.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 }))
{
    using (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
        }
    }
}

Il est également possible de s’inscrire dans une transaction explicite.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
    }
}

Limitations de System.TransactionsLimitations of System.Transactions

  1. EF Core s’appuie sur les fournisseurs de base de données pour implémenter la prise en charge de System.Transactions.EF Core relies on database providers to implement support for System.Transactions. Bien que la prise en charge est assez courante parmi les fournisseurs ADO.NET de .NET Framework, l’API n’a été que récemment ajoutée à .NET Core et n’est par conséquent pas aussi répandue.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 as widespread. Si un fournisseur n’implémente pas la prise en charge de System.Transactions, il est possible que les appels à ces API soient complètement ignorés.If a provider does not implement support for System.Transactions, it is possible that calls to these APIs will be completely ignored. SqlClient pour .NET Core le prend en charge à compter de la version 2.1 et versions ultérieures.SqlClient for .NET Core does support it from 2.1 onwards. SqlClient pour .NET Core 2.0 lève une exception en cas de tentative d’utilisation de la fonctionnalité.SqlClient for .NET Core 2.0 will throw an exception of you attempt to use the feature.

    Important

    Il est recommandé de vérifier que l’API se comporte correctement avec votre fournisseur avant de l’utiliser pour la gestion des transactions.It is recommended that you test that the API behaves correctly with your provider before you rely on it for managing transactions. Nous vous invitons à contacter le chargé de maintenance du fournisseur de base de données si ce n’est pas le cas.You are encouraged to contact the maintainer of the database provider if it does not.

  2. À compter de la version 2.1, l’implémentation de System.Transactions dans .NET Core n’inclut pas de prise en charge des transactions distribuées. Par conséquent, vous ne pouvez pas utiliser TransactionScope ou CommittableTransaction pour coordonner des transactions sur plusieurs gestionnaires de ressources.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 CommittableTransaction to coordinate transactions across multiple resource managers.