Za pomocą transakcjiUsing Transactions

Transakcje pozwalają na wykonanie kilku operacji na bazie danych w sposób niepodzielny.Transactions allow several database operations to be processed in an atomic manner. Jeżeli transakcja zostanie zatwierdzona, wszystkie operacje zostaną pomyślnie wykonane na bazie danych.If the transaction is committed, all of the operations are successfully applied to the database. Jeśli transakcja zostanie wycofana, żadna z operacji nie zostanie wykonana na bazie danych.If the transaction is rolled back, none of the operations are applied to the database.

Porada

Przykład użyty w tym artykule można zobaczyć w witrynie GitHub.You can view this article's sample on GitHub.

Domyślne zachowanie transakcjiDefault transaction behavior

Domyślnie, jeśli dostawca bazy danych obsługuje transakcje, wszystkie zmiany w pojedynczym wywołaniu operacji SaveChanges() są stosowane w transakcji.By default, if the database provider supports transactions, all changes in a single call to SaveChanges() are applied in a transaction. Jeżeli jakakolwiek z tych zmian nie powiedzie się, transakcja zostanie wycofana i zmiany nie zostaną zapisane.If any of the changes fail, then the transaction is rolled back and none of the changes are applied to the database. Gwarantuje to, że operacja SaveChanges() albo powiedzie się całkowicie, albo spowoduje pozostawienie niezmodyfikowanej bazy danych w przypadku błędu.This means that SaveChanges() is guaranteed to either completely succeed, or leave the database unmodified if an error occurs.

W przypadku większości zastosowań to domyślne zachowanie jest wystarczające.For most applications, this default behavior is sufficient. Transakcje powinny być sterowane ręcznie, tylko jeżeli taka konieczność wynika z wymagań danego zastosowania.You should only manually control transactions if your application requirements deem it necessary.

Kontrolowanie transakcjiControlling transactions

Transakcje można rozpoczynać, zatwierdzać i wycofywać w interfejsie API DbContext.Database.You can use the DbContext.Database API to begin, commit, and rollback transactions. Poniższy przykład przedstawia dwie operacje SaveChanges() i zapytanie LINQ wykonane w ramach jednej transakcji.The following example shows two SaveChanges() operations and a LINQ query being executed in a single transaction.

Nie wszyscy dostawcy bazy danych obsługują transakcje.Not all database providers support transactions. Niektórzy dostawcy mogą zgłaszać wyjątki lub nie wykonywać żadnych operacji po wywołaniu interfejsu API transakcji.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
        }
    }
}

Transakcja międzykontekstowa (tylko relacyjne bazy danych)Cross-context transaction (relational databases only)

Transakcje mogą być również współdzielone przez wiele wystąpień kontekstów.You can also share a transaction across multiple context instances. Ta funkcja jest dostępna tylko w przypadku korzystania z dostawcy relacyjnej bazy danych, ponieważ wymaga użycia parametrówDbTransaction i DbConnection, które są specyficzne dla relacyjnych baz danych.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.

Aby współdzielić transakcję, konteksty muszą współdzielić zarówno parameter DbConnection, jak i DbTransaction.To share a transaction, the contexts must share both a DbConnection and a DbTransaction.

Zezwalanie na dostarczanie połączenia z zewnątrzAllow connection to be externally provided

Współdzielenie parametru DbConnection wymaga, aby była możliwość przekazania połączenia do kontekstu podczas jego tworzenia.Sharing a DbConnection requires the ability to pass a connection into a context when constructing it.

Najprostszym sposobem na to, aby dostarczanie parametru DbConnection było możliwe z zewnątrz, jest zrezygnowanie z używania metody DbContext.OnConfiguring do konfigurowania kontekstu i utworzenie opcji DbContextOptions na zewnątrz, a następnie przekazanie ich do konstruktora kontekstu.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.

Porada

Parametr DbContextOptionsBuilder jest interfejsem API używanym w parametrze DbContext.OnConfiguring do konfigurowania kontekstu. Zostanie on teraz użyty zewnętrznie do utworzenia parametru 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; }
}

Alternatywą jest dalsze używanie parametru DbContext.OnConfiguring, ale akceptowanie parametru DbConnection, który jest zapisywany i następnie używany w parametrze 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);
    }
}

Współdzielenie połączenia i transakcjiShare connection and transaction

Teraz możesz utworzyć wiele wystąpień kontekstu współużytkujących to samo połączenie.You can now create multiple context instances that share the same connection. Następnie użyj interfejsu API DbContext.Database.UseTransaction(DbTransaction) do zarejestrowania wielu kontekstów do jednej transakcji.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
        }
    }
}

Korzystanie z zewnętrznych transakcji DbTransaction (tylko relacyjne bazy danych)Using external DbTransactions (relational databases only)

Korzystając z wielu metod dostępu do danych w relacyjnej bazie danych, można współdzielić transakcje z operacjami wykonywanymi przez te różne metody.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.

Poniższy przykład przedstawia sposób wykonywania operacji ADO.NET SqlClient i Entity Framework Core w tej samej transakcji.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
        }
    }
}

Używanie System.TransactionsUsing System.Transactions

Uwaga

Ta funkcja jest nowa na platformie EF Core 2.1.This feature is new in EF Core 2.1.

Istnieje możliwość użycia transakcji otoczenia, aby umożliwić koordynację na większą skalę.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
        }
    }
}

Istnieje również możliwość zarejestrowania w transakcji jawnej.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
    }
}

Ograniczenia przestrzeni nazw System.TransactionsLimitations of System.Transactions

  1. Platforma EF Core korzysta z dostawców baz danych do implementowania obsługi przestrzeni nazw System.Transactions.EF Core relies on database providers to implement support for System.Transactions. Mimo że dostawcy ADO.NET często obsługują platformę .NET Framework, ten interfejs API został dopiero niedawno dodany do platformy EF Core.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. Jeżeli dostawca nie implementuje obsługi przestrzeni nazw System.Transactions, możliwe jest, że wywołania do tego interfejsu API zostaną zignorowane.If a provider does not implement support for System.Transactions, it is possible that calls to these APIs will be completely ignored. SqlClient dla platformy .NET Core obsługuje je od wersji 2.1 w górę.SqlClient for .NET Core does support it from 2.1 onwards. SqlClient dla platformy .NET Core 2.0 zgłosi wyjątek w przypadku próby skorzystania z tej funkcji.SqlClient for .NET Core 2.0 will throw an exception of you attempt to use the feature.

    Ważne

    Zaleca się przetestowanie, czy ten interfejs API działa poprawnie z Twoim dostawcą, zanim skorzystasz z niego do zarządzania transakcjami.It is recommended that you test that the API behaves correctly with your provider before you rely on it for managing transactions. W przypadku problemów zachęcamy do kontaktu z osobami obsługującymi danego dostawcę bazy danych.You are encouraged to contact the maintainer of the database provider if it does not.

  2. Począwszy od wersji 2.1 implementacja przestrzeni nazw System.Transactions na platformie .NET Core nie obsługuje transakcji rozproszonych, dlatego nie można używać parametrów TransactionScope lub CommittableTransaction do koordynowania transakcji w wielu menedżerach zasobów. 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.