Обработка конфликтов параллелизма (EF6)

Оптимистическая параллелизм включает оптимистическую попытку сохранить сущность в базе данных в надежде, что данные не изменились с момента загрузки сущности. Если оказывается, что данные изменились, создается исключение, и необходимо устранить конфликт перед попыткой сохранить еще раз. В этом разделе описывается обработка таких исключений в Entity Framework. Методы, представленные в этом разделе, также применимы к моделям, созданным с помощью Code First и конструктора EF.

Эта должность не является подходящим местом для полного обсуждения оптимистического параллелизма. В приведенных ниже разделах предполагается, что некоторые знания о разрешении параллелизма и отображение шаблонов для распространенных задач.

Многие из этих шаблонов используют темы, рассмотренные в разделе "Работа со значениями свойств".

Устранение проблем параллелизма при использовании независимых ассоциаций (где внешний ключ не сопоставлен со свойством в сущности) гораздо сложнее, чем при использовании связей внешнего ключа. Таким образом, если вы собираетесь выполнить разрешение параллелизма в приложении, рекомендуется всегда сопоставлять внешние ключи с сущностями. Все приведенные ниже примеры предполагают, что вы используете связи внешнего ключа.

DbUpdateConcurrencyException создается SaveChanges при обнаружении исключения оптимистического параллелизма при попытке сохранить сущность, использующую связи внешнего ключа.

Разрешение исключений оптимистического параллелизма с помощью перезагрузки (wins базы данных)

Метод reload можно использовать для перезаписи текущих значений сущности со значениями в базе данных. Затем сущность обычно возвращается пользователю в некоторой форме, и они должны попытаться снова внести изменения и повторно сохранить. Например:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);
    blog.Name = "The New ADO.NET Blog";

    bool saveFailed;
    do
    {
        saveFailed = false;

        try
        {
            context.SaveChanges();
        }
        catch (DbUpdateConcurrencyException ex)
        {
            saveFailed = true;

            // Update the values of the entity that failed to save from the store
            ex.Entries.Single().Reload();
        }

    } while (saveFailed);
}

Хорошим способом имитации исключения параллелизма является установка точки останова в вызове SaveChanges, а затем изменение сущности, сохраненной в базе данных, с помощью другого средства, например SQL Server Management Studio. Можно также вставить строку перед СохранениемChanges, чтобы обновить базу данных непосредственно с помощью SqlCommand. Например:

context.Database.SqlCommand(
    "UPDATE dbo.Blogs SET Name = 'Another Name' WHERE BlogId = 1");

Метод Entries в DbUpdateConcurrencyException возвращает экземпляры DbEntityEntry для сущностей, которые не удалось обновить. (В настоящее время это свойство всегда возвращает одно значение для проблем параллелизма. Он может возвращать несколько значений для общих исключений обновлений.) Альтернативой для некоторых ситуаций может быть получение записей для всех сущностей, которые могут потребоваться перезагрузить из базы данных и перезагрузить для каждой из этих сущностей.

Разрешение исключений оптимистического параллелизма при победе клиента

Приведенный выше пример, использующий перезагрузку, иногда называется победами базы данных или сохраняет победы, так как значения в сущности перезаписываются значениями из базы данных. Иногда может потребоваться выполнить противоположное действие и перезаписать значения в базе данных значениями, которые в данный момент находятся в сущности. Иногда это называется победами клиента, и их можно сделать, получив текущие значения базы данных и установив их в качестве исходных значений для сущности. (См. раздел Работа со значениями свойств для получения сведений о текущих и исходных значениях.) Например:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);
    blog.Name = "The New ADO.NET Blog";

    bool saveFailed;
    do
    {
        saveFailed = false;
        try
        {
            context.SaveChanges();
        }
        catch (DbUpdateConcurrencyException ex)
        {
            saveFailed = true;

            // Update original values from the database
            var entry = ex.Entries.Single();
            entry.OriginalValues.SetValues(entry.GetDatabaseValues());
        }

    } while (saveFailed);
}

Настраиваемое разрешение исключений оптимистического параллелизма

Иногда может потребоваться объединить значения в базе данных с значениями, которые в данный момент находятся в сущности. Обычно это требует пользовательской логики или взаимодействия с пользователем. Например, вы можете представить пользователю форму, содержащую текущие значения, значения в базе данных и набор разрешенных значений по умолчанию. Затем пользователь отредактирует разрешенные значения по мере необходимости, и это будут эти разрешенные значения, которые сохраняются в базе данных. Это можно сделать с помощью объектов DbPropertyValues, возвращаемых из CurrentValues и GetDatabaseValues в записи сущности. Например:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);
    blog.Name = "The New ADO.NET Blog";

    bool saveFailed;
    do
    {
        saveFailed = false;
        try
        {
            context.SaveChanges();
        }
        catch (DbUpdateConcurrencyException ex)
        {
            saveFailed = true;

            // Get the current entity values and the values in the database
            var entry = ex.Entries.Single();
            var currentValues = entry.CurrentValues;
            var databaseValues = entry.GetDatabaseValues();

            // Choose an initial set of resolved values. In this case we
            // make the default be the values currently in the database.
            var resolvedValues = databaseValues.Clone();

            // Have the user choose what the resolved values should be
            HaveUserResolveConcurrency(currentValues, databaseValues, resolvedValues);

            // Update the original values with the database values and
            // the current values with whatever the user choose.
            entry.OriginalValues.SetValues(databaseValues);
            entry.CurrentValues.SetValues(resolvedValues);
        }
    } while (saveFailed);
}

public void HaveUserResolveConcurrency(DbPropertyValues currentValues,
                                       DbPropertyValues databaseValues,
                                       DbPropertyValues resolvedValues)
{
    // Show the current, database, and resolved values to the user and have
    // them edit the resolved values to get the correct resolution.
}

Настраиваемое разрешение исключений оптимистического параллелизма с помощью объектов

Приведенный выше код использует экземпляры DbPropertyValues для передачи текущих, баз данных и разрешенных значений. Иногда для этого может быть проще использовать экземпляры типа сущности. Это можно сделать с помощью методов ToObject и SetValues dbPropertyValues. Например:

using (var context = new BloggingContext())
{
    var blog = context.Blogs.Find(1);
    blog.Name = "The New ADO.NET Blog";

    bool saveFailed;
    do
    {
        saveFailed = false;
        try
        {
            context.SaveChanges();
        }
        catch (DbUpdateConcurrencyException ex)
        {
            saveFailed = true;

            // Get the current entity values and the values in the database
            // as instances of the entity type
            var entry = ex.Entries.Single();
            var databaseValues = entry.GetDatabaseValues();
            var databaseValuesAsBlog = (Blog)databaseValues.ToObject();

            // Choose an initial set of resolved values. In this case we
            // make the default be the values currently in the database.
            var resolvedValuesAsBlog = (Blog)databaseValues.ToObject();

            // Have the user choose what the resolved values should be
            HaveUserResolveConcurrency((Blog)entry.Entity,
                                       databaseValuesAsBlog,
                                       resolvedValuesAsBlog);

            // Update the original values with the database values and
            // the current values with whatever the user choose.
            entry.OriginalValues.SetValues(databaseValues);
            entry.CurrentValues.SetValues(resolvedValuesAsBlog);
        }

    } while (saveFailed);
}

public void HaveUserResolveConcurrency(Blog entity,
                                       Blog databaseValues,
                                       Blog resolvedValues)
{
    // Show the current, database, and resolved values to the user and have
    // them update the resolved values to get the correct resolution.
}