ノンリピータブル リードとその解決方法 その 2

前回のノンリピータブル リードとその解決方法 その 1 では SqlCommand クラスを利用したコードを示しましたが、
今回は ADO.NET Entity Framework を利用したコードを示します。
ただし、特に説明する部分はないので、ステップ 2 (Repeatable Read) の場合のみ示します。

なお、下記のコードを記述する前に、ロスト アップデートとその解決方法 その 3 と同様に
Visual Studio で ADO.NET Entity Data Model を作成しておきます。


using System;
using System.Data.Objects;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;

namespace NonrepeatableRead

    class Program
    {
        private static readonly TransactionOptions transactionOptions = new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }; 

        private static int isNonrepeatableCount;

        static void Main(string[] args)
        {
            // 更新と読み取りを並列に実行します。
            Parallel.Invoke(
                () =>
                {
                    for (int i = 0; i < 200; i++)
                    {
                        UpdateUnitsInStock();
                    }
                },
                () =>
                {
                    for (int i = 0; i < 100; i++)
                    {
                        SelectUnitsInStock();
                    }
                }
            );
        }

        /// <summary>
        /// Northwind データベースの Products テーブルの UnitsInStock の値を更新します。
        /// </summary>
        private static void UpdateUnitsInStock()
        {
            using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
            using (var context = new NorthwindEntities())
            {
                // 在庫個数を更新します。
                Product product = context.Products.Single(p => p.ProductID == 1);
                product.UnitsInStock += 1;
                context.SaveChanges();

                // コミットします。
                scope.Complete();
            }
        }

        /// <summary>
        /// Northwind データベースの Products テーブルの UnitsInStock の値を読み取ります。
        /// </summary>
        private static void SelectUnitsInStock()
        {
            using (var scope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
            using (var context = new NorthwindEntities())
            {
                // 在庫個数を読み取ります。
                Product product = context.Products.Single(p => p.ProductID == 1);
                short unitsInStock1 = product.UnitsInStock.Value;

                context.Refresh(RefreshMode.StoreWins, product);
                short unitsInStock2 = product.UnitsInStock.Value;

                if (unitsInStock1 != unitsInStock2)
                {
                    Console.WriteLine("{0} = {1} ({2} 回目)", unitsInStock1, unitsInStock2, ++isNonrepeatableCount);
                }
                else
                {
                    Console.WriteLine("{0} = {1}", unitsInStock1, unitsInStock2);
                }
            }
        }
    }
}


注意点
(1) ObjectContext.Refresh メソッドの引数に RefreshMode 列挙体の値を指定しなければなりませんが、
     今回の場合はクライアント側で値を変更していないため、
     StoreWins、ClientWins のうちどちらを指定しても同じ結果となります。

バージョン情報
.NET Framework 4
SQL Server 2008, 2008 R2

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。