web-dev-qa-db-ja.com

Entity Framework 6トランザクションのロールバック

EF6では、次のように使用できる新しいトランザクションがあります。

using (var context = new PostEntityContainer())
        {
            using (var dbcxtransaction = context.Database.BeginTransaction())
            {
                try
                {
                    PostInformation NewPost = new PostInformation()
                    {
                        PostId = 101,
                        Content = "This is my first Post related to Entity Model",
                        Title = "Transaction in EF 6 beta"
                    };
                    context.Post_Details.Add(NewPost);
                    context.SaveChanges();
                    PostAdditionalInformation PostInformation = new PostAdditionalInformation()
                    {
                        PostId = (101),
                        PostName = "Working With Transaction in Entity Model 6 Beta Version"
                    };

                    context.PostAddtional_Details.Add(PostInformation);
                    context.SaveChanges();

                    dbcxtransaction.Commit();
                }
                catch
                {
                    dbcxtransaction.Rollback();
                }
            }
        }

物事が横向きになったときに実際にロールバックが必要ですか? Commitの説明に「基になるストアトランザクションをコミットする」と書かれているので、興味があります。

一方、ロールバックの説明では、「基になるストアトランザクションをロールバックします。」

コミットが呼び出されない場合、以前に実行されたコマンドが保存されないように思えるので、これは私を不思議にさせます(私にとっては理にかなっています)。しかし、その場合、ロールバック関数を呼び出す理由は何でしょうか? EF5では、ロールバック機能(完全のみ)を持たないTransactionScopeを使用しました。 MS DTCの理由により、TransactionScopeは使用できなくなりましたが、上記の例のようなtry catchも使用できません(つまり、Commitのみが必要です)。

72
The Cookies Dog

Rollbackステートメントを使用しているため、usingを手動で呼び出す必要はありません。

DbContextTransaction.Disposeメソッドは、usingブロックの最後に呼び出されます。また、トランザクションが正常にコミットされなかった場合(呼び出されなかったり、例外が発生しなかった場合)、トランザクションを自動的にロールバックします。以下はSqlInternalTransaction.Disposeメソッドのソースコードです(DbContextTransaction.DisposeはSqlServerプロバイダーの使用時に最終的に委任されます):

private void Dispose(bool disposing)
{
    // ...
    if (disposing && this._innerConnection != null)
    {
        this._disposing = true;
        this.Rollback();
    }
}

_innerConnectionがnullでないかどうかを確認し、そうでない場合は、トランザクションをロールバックします(コミットされた場合、_innerConnectionはnullになります)。 Commitの機能を見てみましょう。

internal void Commit() 
{
    // Ignore many details here...

    this._innerConnection.ExecuteTransaction(...);

    if (!this.IsZombied && !this._innerConnection.IsYukonOrNewer)
    {
        // Zombie() method will set _innerConnection to null
        this.Zombie();
    }
    else
    {
        this.ZombieParent();
    }

    // Ignore many details here...
}

internal void Zombie()
{
    this.ZombieParent();

    SqlInternalConnection innerConnection = this._innerConnection;

    // Set the _innerConnection to null
    this._innerConnection = null;

    if (innerConnection != null)
    {
        innerConnection.DisconnectTransaction(this);
    }
}
105
Mouhong Lin

常にEFと共にSQL Serverを使用する限り、catchを明示的に使用してRollbackメソッドを呼び出す必要はありません。 usingブロックが例外を自動的にロールバックできるようにすると、常に機能します。

ただし、Entity Frameworkの観点から考えると、すべての例で明示的な呼び出しを使用してトランザクションをロールバックする理由を確認できます。 EFにとって、データベースプロバイダーは任意でプラグイン可能であり、プロバイダーはMySQLまたはEFプロバイダー実装を持つ他のデータベースに置き換えることができます。したがって、EFの観点からは、EFはデータベースプロバイダーの実装を知らないため、プロバイダーが破棄されたトランザクションを自動的にロールバックするという保証はありません。

したがって、EFのドキュメントでは、ベストプラクティスとして、明示的にロールバックすることを推奨しています-いつかプロバイダを破棄時に自動ロールバックしない実装に変更する場合に備えて。

私の意見では、よく書かれた適切なプロバイダーは破棄でトランザクションを自動的にロールバックするため、使用ブロック内のすべてをtry-catch-rollbackでラップする追加の労力は過剰です。

21
Rwb
  1. トランザクションをインスタンス化するために「使用」ブロックを記述したので、Rollback関数を明示的に言及する必要はありません。これは、破棄時に自動的にロールバックされる(コミットされない限り)ためです。
  2. ただし、usingブロックを使用せずにインスタンス化する場合、例外の場合(正確にはcatchブロック内)にトランザクションをロールバックすることが不可欠であり、さらに堅牢なコードのnullチェックを使用します。 BeginTransactionの動作は、トランザクションスコープ(すべての操作が正常に完了した場合に完全な機能を必要とするだけ)とは異なります。代わりに、SQLトランザクションの動作に似ています。
4
roopaliv