.NET Core, Entity Framework Coreでトランザクション制御を行う


はじめに

.NET Core の Entity Framework Core では、デフォルトでトランザクション制御が行われます。

具体的には、MyContext#SaveChange メソッドが呼び出された際にトランザクションが開始され、データの更新が成功すればコミット、失敗すればロールバックされ例外がスローされます。このトランザクション処理については、以下の記事を参考にしてください。

とはいえ、複数回 MyContext#SaveChange メソッドを呼び出すこともあるでしょうし、トランザクションの分離レベルを指定したい場合もあるでしょうから、明示的にトランザクションを制御する方法は必要です。

この記事では、.NET Core のコンソールアプリケーションで、コードファーストからのトランザクション制御をする方法をご紹介します。

なお、環境は Mac (macOS High Sierra 10.13.3) で .NET Core SDK 2.1.4、PostgreSQL 10.3 、Visual Studio Code になります。とはいえ、Windows でも同じように動作すると思います。

コンソールアプリケーションの作成

以下のコマンドでコンソールアプリケーションを作成します。

$ dotnet new console -o TranTest

パッケージのインストール

Nuget から2つのパッケージをインストールします。

$ dotnet add package Microsoft.EntityFrameworkCore.Tools.DotNet
$ dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL

これだけでは設定が足りないので、.csproj を以下のように変更します。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.2" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.0.1" />
  </ItemGroup>

  <!--ここを追記-->
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" />
  </ItemGroup>
</Project>

コードファーストによるデータベース構築

では、コードファーストによるデータベース構築を行います。

コードファーストとは、ソースコードの内容をデータベースに反映することでデータベースを構築するという比較的最近の手法です。ラピッドリリースな世界でよく使われているようですね。

モデルクラスとして、Person.cs を作成します。

using System;

namespace TranTest.Models {
    public class Person
    {
        public int Id {get ; set;}
        public string Name {get; set;}
        public int Age {get; set;}
    }
}

そして、データベースコンテキストクラスとして、MyContext.cs を作成します。

using System;
using Microsoft.EntityFrameworkCore;

namespace TranTest.Models {
    public class MyContext : DbContext
    {
        public DbSet<Person> Person { get; set; }

         protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseNpgsql(@"Server=localhost;Database=trantest;Username=username;Password=password;");
        }
    }
}

これでデータベースを作成する準備が整ったので、以下のコマンドでデータベースを作成します。init は重複しなければよいので分かりやすい名称をつけてください。

$ dotnet ef migrations add init
$ dotnet ef database update

データベースが作成できたので準備完了です。

トランザクションの実装

トランザクションの実装は以下のように行います。

using System;
using Microsoft.EntityFrameworkCore;
using Npgsql.EntityFrameworkCore.PostgreSQL;
using TranTest.Models;

namespace TranTest
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new MyContext())
            {
               using (var transaction = context.Database.BeginTransaction())
               {
                   try {
                        context.Add(new Person{Name = "田中", Age = 20});
                        context.Add(new Person{Name = "佐藤", Age = 31});
                        context.Add(new Person{Name = "鈴木", Age = 44});
                        context.SaveChanges();

                        //Rollback Test
                        //throw new Exception("Error happened!!");

                        transaction.Commit();

                        //Check
                        foreach(var person in context.Person) {
                            Console.WriteLine("Name:{0}, Age:{1}", person.Name, person.Age);
                        }

                   } catch (Exception ex) {
                       Console.WriteLine(ex.Message);
                       transaction.Rollback();
                   }
               }
            }
        }
    }
}

このソースコードでは通常ではデータの更新が成功します。

実行結果は以下のようになりましたので問題ありませんね。

$ dotnet run
Name:田中, Age:20
Name:佐藤, Age:31
Name:鈴木, Age:44

これでトランザクション制御は問題ないですね。

トランザクション分離レベル

トランザクション分離レベルの詳細については、Wikipedia のトランザクション分離レベルの項目を参照してほしいのですが、トランザクション分離レベルには以下の4つがあります。

  • SERIALIZABLE
  • REPEATABLE READ
  • READ COMMITTED
  • READ UNCOMMITTED

一番強度が強いのが SERIALIZABLE で、一般的に使用されているのが READ COMMITTED だと思います。データベースのデフォルトのトランザクション分離レベルが READ COMMITTED という場合も多いと思います。PostgreSQL も READ COMMITTED がデフォルトになっています。

ですが、パフォーマンスを落としてでも絶対的に整合性を取らなければならない場合は、SERIALIZABLE を指定する必要があります。

では、Entity Framework Core でどうやってトランザクション分離レベルを実現するかということですが、意外にも簡単に実装できます。具体的には、先ほどのソースコードに以下の2行の変更をするだけです。今回は READ COMMITTED を指定してみました。

using System.Data; //追加

//(中略)

  using (var transaction = context.Database.BeginTransaction(IsolationLevel.ReadCommitted))

トランザクション分離レベルは理解が難しいですが、データの整合性を取るためにも理解しておいたほうがよいかと思います。

おわりに

.NET Core の Entity Framework Core でのトランザクション制御の方法について見てきましたが、いかがだったでしょうか。

意外に簡単だと思われた方も多いでしょうね。

よくできた実装だと思います。

うまく活用して安全なアプリケーションを構築したいですね。

参考サイト