同時実行の処理Handling Concurrency

プロパティが、同時実行トークンとして構成されている場合 EF は、その他のユーザーが変更なし、データベース内でその値そのレコードに変更を保存するときにでチェックされます。If a property is configured as a concurrency token then EF will check that no other user has modified that value in the database when saving changes to that record.

ヒント

この記事を表示するサンプルGitHub でします。You can view this article's sample on GitHub.

EF Core での同時実行処理のしくみHow concurrency handling works in EF Core

Entity Framework Core での同時実行処理のしくみの詳細については、次を参照してください。同時実行トークンです。For a detailed description of how concurrency handling works in Entity Framework Core, see Concurrency Tokens.

同時実行の競合を解決します。Resolving concurrency conflicts

同時実行の競合を解決するには、データベースに加えられた変更と、現在のユーザーから、保留中の変更をマージするアルゴリズムを使用してが含まれます。Resolving a concurrency conflict involves using an algorithm to merge the pending changes from the current user with the changes made in the database. 正確な方法、アプリケーションは異なりますが、一般的な方法をユーザーに値を表示し、データベースに格納するための正しい値を決定する、です。The exact approach will vary based on your application, but a common approach is to display the values to the user and have them decide the correct values to be stored in the database.

同時実行の競合を解決するために使用可能な値の 3 つのセットがあります。There are three sets of values available to help resolve a concurrency conflict.

  • 現在の値値、アプリケーションがデータベースへの書き込みを試みたを示します。Current values are the values that the application was attempting to write to the database.

  • 元の値はすべての編集が行われる前に、データベースから取得された元の値。Original values are the values that were originally retrieved from the database, before any edits were made.

  • 値をデータベースはデータベースに格納されている値です。Database values are the values currently stored in the database.

同時実行の競合を処理する catch、DbUpdateConcurrencyException中にSaveChanges()を使用してDbUpdateConcurrencyException.Entriesを影響を受けるエンティティでは、一連の変更を準備し、再試行、SaveChanges()操作します。To handle a concurrency conflict, catch a DbUpdateConcurrencyException during SaveChanges(), use DbUpdateConcurrencyException.Entries to prepare a new set of changes for the affected entities, and then retry the SaveChanges() operation.

次の例では、Person.FirstNamePerson.LastName同時実行トークンとしてセットアップします。In the following example, Person.FirstName and Person.LastName are setup as concurrency token. // TODO:をデータベースに保存する値を選択するアプリケーション固有のロジックを含めると、場所にコメントです。There is a // TODO: comment in the location where you would include application specific logic to choose the value to be saved to the database.

using Microsoft.EntityFrameworkCore;
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace EFSaving.Concurrency
{
    public class Sample
    {
        public static void Run()
        {
            // Ensure database is created and has a person in it
            using (var context = new PersonContext())
            {
                context.Database.EnsureDeleted();
                context.Database.EnsureCreated();

                context.People.Add(new Person { FirstName = "John", LastName = "Doe" });
                context.SaveChanges();
            }

            using (var context = new PersonContext())
            {
                // Fetch a person from database and change phone number
                var person = context.People.Single(p => p.PersonId == 1);
                person.PhoneNumber = "555-555-5555";

                // Change the persons name in the database (will cause a concurrency conflict)
                context.Database.ExecuteSqlCommand("UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");

                try
                {
                    // Attempt to save changes to the database
                    context.SaveChanges();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    foreach (var entry in ex.Entries)
                    {
                        if (entry.Entity is Person)
                        {
                            // Using a NoTracking query means we get the entity but it is not tracked by the context
                            // and will not be merged with existing entities in the context.
                            var databaseEntity = context.People.AsNoTracking().Single(p => p.PersonId == ((Person)entry.Entity).PersonId);
                            var databaseEntry = context.Entry(databaseEntity);

                            foreach (var property in entry.Metadata.GetProperties())
                            {
                                var proposedValue = entry.Property(property.Name).CurrentValue;
                                var originalValue = entry.Property(property.Name).OriginalValue;
                                var databaseValue = databaseEntry.Property(property.Name).CurrentValue;

                                // TODO: Logic to decide which value should be written to database
                                // entry.Property(property.Name).CurrentValue = <value to be saved>;

                                // Update original values to
                                entry.Property(property.Name).OriginalValue = databaseEntry.Property(property.Name).CurrentValue;
                            }
                        }
                        else
                        {
                            throw new NotSupportedException("Don't know how to handle concurrency conflicts for " + entry.Metadata.Name);
                        }
                    }

                    // Retry the save operation
                    context.SaveChanges();
                }
            }
        }

        public class PersonContext : DbContext
        {
            public DbSet<Person> People { get; set; }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFSaving.Concurrency;Trusted_Connection=True;");
            }
        }

        public class Person
        {
            public int PersonId { get; set; }

            [ConcurrencyCheck]
            public string FirstName { get; set; }

            [ConcurrencyCheck]
            public string LastName { get; set; }

            public string PhoneNumber { get; set; }
        }

    }
}