EFCoreでAPI通信を含むトランザクションに対応する

Entity Framework Core(EF Core)を使っていると、
トランザクション処理を勝手にやってくれるのではありますが、
時として、自分で管理したいことがありませんか。

他社のサービスと連携しているなどの場合は、API通信が挟まるため、
明示的にトランザクションを管理し、ロールバックしないとデータの整合性が保てなくなります。

こんな時は、トランザクションをきちんと管理しないといけません。

そこで、今回はEntity Framework Core(EF Core)で利用できる
トランザクション管理の仕方を紹介します。

SaveChangesメソッドでトランザクション管理されている

EF CoreのSaveChangesメソッドはトランザクション開始、コミット、ロールバックを
よきにやってくれるため、意識せずとも、トランザクションを管理してくれます。

例えば、次のようなStudentクラスを更新してみましょう。


データベースに新しいレコードを登録と、
既存のデータを更新は次のように書くことができます。


このように、SaveChangesメソッド複数の変更を一度にデータベースへ伝えることができ、

更新が成功するなら両方成功。
片方が失敗したならば、ロールバックで両方失敗にすることが可能です。

SaveChangesメソッドは暗黙的なトランザクション

つまり、SaveChangesメソッドでトランザクションが完結しているため、
API通信をはさむ場合は対応することができません。

SaveChangeメソッドでトランザクションを完結させないためにも、
明示的なトランザクションを利用して、自在にロールバックできるようにします。

明示的なトランザクションを利用する方法2つ

ではまずは、トランザクションの管理する方法から確認です。

明示的にトランザクション管理する方法を2つ紹介します。

  • DbcontextからBeginTransactionメソッドでトランザクション管理
  • TransactionScopeでトランザクション管理

DbcontextからBeginTransactionメソッドを呼ぶ


Dbcontextからトランザクションメソッドを呼ぶコードは以上です。

ポイントを見ていきましょう。

ポイント:BeginTransactionメソッド

まずはDbContextからBeginTransactionを呼び出すことで、

明示的にトランザクションが開始されます。

ポイント:using var

using varの宣言で、「今回のスコープ限定でトランザクションをはります」を宣言しています。

スコープを抜けるとトランザクションが終了します。

なお、「tran.Commit()」で明示的にコミットをしないと、ロールバックが自動で行われます。

補足:明示的にロールバック


ちなみに、明示的にロールバックをしたいときは「tran.Rollback();」で可能です。

「tran.Commit();」の代わりに呼び出せば、その時点でロールバックされるので、
スコープを抜ける前にロールバックできます。

TransactionScopeでトランザクション管理

DbContextからトランザクションを利用するのではなく、

TransactionScopeというクラスを利用することでも、トランザクションを明示的に作れます。


scope.Complete();が呼ばれた場合のみコミット、それ以外はずべてロールバックです。

この辺りは、DbContextと共通ですね。

TransactionScopeの特徴

同じトランザクションを使う機能ですが

「先ほどのDbContextのBeginTransactionから始めるトランザクションとの使い分けは?」

と疑問に持たれるかもしれません。

TransactionScopeとBeginTransactionメソッドの違いをあげるとすると

「DbContext以外でデータベースに問い合わせている場合にTransactionScopeは対応できる」

です。

例を挙げると、DbContext + SqlCommandの併用している場合です。



このように、別々の仕組みをつかってデータベースにクエリを発行しているもの同士を
TransactionScopeに含めることで、同じトランザクション内で取り扱うことが可能になります。


ちなみに、TransactionScopeはSystem.Transactionsにありますので、利用してみてください。

なぜ明示的になトランザクションが必要なのか

なぜ、明示的になトランザクションが必要なのでしょうか。

理由は、データを保存している場所がデータベース一つじゃないときに整合性が崩れるからです。

逆に、データベースが一つの場合は、SaveCahgesメソッドで十分対応可能です。

ですが、SaveChangesメソッドでトランザクション管理できないパターン、
データベース以外のものと通信しているときは要注意です。

通信した先に、データ保存をされている可能性があります。

外部サービスと連携しているとき

データベースへの通信はトランザクション管理が可能です。

しかし、外部のサービスをhttp(s)で通信しているとトランザクションが使えません。

つまりデータベースが外にある時などは整合性を保ちたいところに、トランザクションをはれないので、

自分たちでAPI通信が成功したら、トランザクションをコミット
失敗したらロールバックと整合性を保つ動きを、作りこむ必要が出てきます。

シンプルな例:通信前の状態に戻せるトランザクション例

今回の例は、通信をする前に戻すこのとできるトランザクション例です。
httpClientを使うため非同期メソッドで記載しています。


このコードでの新しいことは「CreateSavepointメソッド」と「RollbackToSavepointメソッド」です。

CreateSavepointメソッドで一時保存する場所をつくり、
RollbackToSavepointメソッドでロールバックする箇所を指定しています。

もちろん、RollbackメソッドでBeginTransactionメソッドまで、戻すこともできます。

通信の性質や整合性の保ちたいポイントによって、コードは変わってくるでしょう。

しかし、基本となる仕組み、BegintTransactionで開始し、
その後はusing var で宣言したトランザクションを利用する仕組みは変わりません。

まとめ

まとめです。

  • なにもしないとSaveChangeだけでトランザクションが完結する
  • トランザクションを制御するにはBeginTransactionメソッドから行える
  • TransactionScopeによるトランザクション制御は、EF Coreを含む複数の仕組みのデータベースアクセスを同時に制御できる

APIが関連してくるトランザクションはとりたい整合性によって変わってきますが、
まずは、この記事がトランザクションの使い方の参考になればうれしいです。

では。