トランザクションの使用Using Transactions

トランザクションは、複数のデータベース操作をアトミックな方法で処理することを可能にします。Transactions allow several database operations to be processed in an atomic manner. トランザクションがコミットされる場合は、すべての操作がデータベースに正常に適用されます。If the transaction is committed, all of the operations are successfully applied to the database. トランザクションがロールバックされるる場合、データベースに適用される操作はありません。If the transaction is rolled back, none of the operations are applied to the database.

ヒント

この記事のサンプルは GitHub で確認できます。You can view this article's sample on GitHub.

既定のトランザクションの動作Default transaction behavior

既定では、データベース プロバイダーがトランザクションをサポートしている場合は、SaveChanges() への 1 回の呼び出しに含まれるすべての変更がトランザクションに適用されます。By default, if the database provider supports transactions, all changes in a single call to SaveChanges() are applied in a transaction. いずれかの変更が失敗した場合、トランザクションはロールバックされ、変更は、データベースにまったく適用されません。If any of the changes fail, then the transaction is rolled back and none of the changes are applied to the database. つまり、SaveChanges() は、完全に成功するか、エラーが発生した場合はデータベースを未変更のままにすることが保証されます。This means that SaveChanges() is guaranteed to either completely succeed, or leave the database unmodified if an error occurs.

ほとんどのアプリケーションでは、この既定の動作で十分です。For most applications, this default behavior is sufficient. アプリケーションの要件を満たすために必要であると考えた場合にのみ、トランザクションを手動で制御する必要があります。You should only manually control transactions if your application requirements deem it necessary.

トランザクションを制御するControlling transactions

トランザクションは、DbContext.Database API を使用して、開始、コミット、およびロールバックできます。You can use the DbContext.Database API to begin, commit, and rollback transactions. 次の例は、単一のトランザクションで実行される 2 つの SaveChanges() 操作と LINQ クエリを示しています。The following example shows two SaveChanges() operations and a LINQ query being executed in a single transaction.

すべてのデータベース プロバイダーがトランザクションをサポートしているわけではありません。Not all database providers support transactions. トランザクション API が呼び出された場合、一部のプロバイダーでは、例外がスローされるか、何もも実行されないことがあります。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
        }
    }
}

クロスコンテキスト トランザクション (リレーショナル データベースのみ)Cross-context transaction (relational databases only)

複数のコンテキスト インスタンス間でトランザクションを共有することもできます。You can also share a transaction across multiple context instances. この機能は、リレーショナル データベースに固有の DbTransactionDbConnection を使用する必要があるため、リレーショナル データベース プロバイダーを使用する場合にのみ利用できます。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.

トランザクションを共有するには、コンテキストが DbConnectionDbTransaction の両方を共有する必要があります。To share a transaction, the contexts must share both a DbConnection and a DbTransaction.

接続の外部提供を可能にするAllow connection to be externally provided

DbConnection の共有では、コンテキストの構築時に、コンテキストに接続を渡すことができるようにする必要があります。Sharing a DbConnection requires the ability to pass a connection into a context when constructing it.

DbConnection の外部提供を許可する最も簡単な方法は、DbContext.OnConfiguring メソッドを使用するコンテキストの構成を停止し、DbContextOptions を外部で作成し、それをコンテキスト コンストラクターに渡すことです。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.

ヒント

DbContextOptionsBuilder は、コンテキストを構成する DbContext.OnConfiguring 内で使用される API であり、その API を外部で使用して、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; }
}

別の方法は、DbContext.OnConfiguring を引き続き使用しますが、保存された DbConnection を受け取って 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);
    }
}

接続とトランザクションの共有Share connection and transaction

同じ接続を共有する複数のコンテキスト インスタンスを作成できるようになりました。You can now create multiple context instances that share the same connection. 次に、DbContext.Database.UseTransaction(DbTransaction) API を使用して、両方のコンテキストを同じトランザクションに参加させます。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
        }
    }
}

外部 DbTransactions の使用 (リレーショナル データベースのみ)Using external DbTransactions (relational databases only)

複数のデータ アクセス テクノロジを使用してリレーショナル データベースにアクセスしている場合、これらの異なるテクノロジによって実行される操作の間でトランザクションを共有できます。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.

次の例は、同じトランザクション内で ADO.NET SqlClient 操作と Entity Framework Core 操作を実行する方法を示しています。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
        }
    }
}

System.Transactions の使用Using System.Transactions

注意

これは EF Core 2.1 の新機能です。This feature is new in EF Core 2.1.

大規模なスコープで調整を行う必要がある場合は、アンビエント トランザクションを使用できます。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
        }
    }
}

明示的なトランザクションに参加することもできます。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
    }
}

System.Transactions の制限Limitations of System.Transactions

  1. EF Core は、System.Transactions に対するサポートの実装をデータベース プロバイダーに依存しています。EF Core relies on database providers to implement support for System.Transactions. .NET Framework の ADO.NET プロバイダーの間では、サポートは非常に一般的ですが、この API は .NET 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. プロバイダーが System.Transactions のサポートを実装していない場合、これらの API への呼び出しは、完全に無視される可能があります。If a provider does not implement support for System.Transactions, it is possible that calls to these APIs will be completely ignored. SqlClient for .NET Core では、2.1 以降、それをサポートします。SqlClient for .NET Core does support it from 2.1 onwards. SqlClient for .NET Core 2.0 では、この機能を使用しようとすると、例外がスローされます。SqlClient for .NET Core 2.0 will throw an exception of you attempt to use the feature.

    重要

    この API に依存してトランザクションを管理する前に、お使いのプロバイダーで API が正常に動作することをテストすることをお勧めします。It is recommended that you test that the API behaves correctly with your provider before you rely on it for managing transactions. そうでない場合は、データベース プロバイダーの保守管理者に連絡することが推奨されます。You are encouraged to contact the maintainer of the database provider if it does not.

  2. バージョン 2.1 の時点では、.NET Core での System.Transactions の実装には、分散トランザクションのサポートは含まれていません。したがって、TransactionScope または CommittableTransaction を使用して複数のリソース マネージャー間でトランザクションを調整することはできません。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.