Entity Framework Самоотношение «многие ко многим» и оптимистичный контроль параллелизма

У меня есть сущность, которая имеет отношение «многие ко многим». В качестве примера рассмотрим эту сущность:

public class User
{
    public int ID { get; set; }
    public string UserName { get; set; }

    public virtual ICollection<User> Friends { get; set; }
}

Вот как я настраиваю сопоставление:

HasMany(t => t.Friends).WithMany()
    .Map(m => { 
        m.MapLeftKey("UserID");
        m.MapRightKey("FriendID");
        m.ToTable("UserFriends");
        });

Поскольку эти отношения теперь управляются EF, у меня действительно нет доступа к UserFriends DbSet в моем коде, и я не могу обрабатывать одновременный доступ к нему. Чтобы эта композиция могла обрабатывать параллельный доступ (добавлять/удалять), нужно ли мне самому обрабатывать отношение «многие ко многим», а затем добавлять столбец [Timestamp] или есть способ указать EF, чтобы он сам обрабатывал это одновременно? Как конфигурация в построителе моделей.

Изменить: я использую EF 6, и в настоящее время, если есть параллельная операция с объектом (например, попытка удалить друга, который в настоящее время не выходит из базы данных), я получаю следующее сообщение об ошибке и DbUpdateException:

Произошла ошибка при сохранении сущностей, которые не предоставляют свойства внешнего ключа для своих связей. Свойство EntityEntries вернет значение null, поскольку один объект не может быть идентифицирован как источник исключения. Обработку исключений при сохранении можно упростить, предоставив свойства внешнего ключа в ваших типах сущностей. Подробнее см. InnerException.


person Farhad Alizadeh Noori    schedule 22.04.2015    source источник
comment
Не могли бы вы подробнее рассказать о том, какой тип параллельного доступа у вас есть и как могут возникнуть конфликты? Например. у вас есть только несколько потоков внутри вашего приложения, добавляющих/удаляющих, или есть процессы вне вашего приложения, которые также изменяют данные?   -  person Asad Saeeduddin    schedule 22.04.2015
comment
Ну, это в основном разные клиенты, изменяющие эти сущности одновременно. Они могут одновременно добавлять/удалять друзей для одного из пользователей, зарегистрированных в системе. Допустим, и клиент А, и клиент Б удаляют одного и того же друга у пользователя. Это вызовет исключение. Однако в настоящее время я получаю только DBUpdateException, а не OptimisticConcurrencyException, с которым можно правильно обращаться.   -  person Farhad Alizadeh Noori    schedule 22.04.2015
comment
Есть ли сообщение, которое вы получаете для DBUpdateException? Кроме того, просто для ясности, какую версию EF вы используете?   -  person Asad Saeeduddin    schedule 22.04.2015
comment
@Asad, пожалуйста, посмотрите мое редактирование запрошенной вами информации.   -  person Farhad Alizadeh Noori    schedule 22.04.2015
comment
К сожалению, я думаю, что то, что вы хотите, невозможно. См. efreversepoco.codeplex.com/workitem/53, который все еще открыт. Я думаю, вам придется явно управлять отображением самостоятельно и добавить поле метки времени.   -  person Asad Saeeduddin    schedule 22.04.2015
comment
@ Асад, я вижу. Добавьте это в ответ, и я приму это.   -  person Farhad Alizadeh Noori    schedule 22.04.2015


Ответы (2)


Оптимистический параллелизм здесь не применяется.

Таблица соединений никогда не обновляется. Его записи либо добавляются, либо удаляются. Это означает, что нет операций CRUD, которым нужна версия строки.

Так что на самом деле параллелизм довольно прост:

  • Два одновременных пользователя не могут добавить одну и ту же ассоциацию, потому что последний столкнется с нарушением уникального ключа.
  • Два одновременных пользователя не могут удалить одну и ту же ассоциацию, потому что последний увидит исключение, касающееся неожиданного количества записей (0).
  • Что касается проблем с внешним ключом (добавление/удаление ассоциации с объектом, который был удален в то же время). Это также вызовет исключения.

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

person Gert Arnold    schedule 22.04.2015
comment
Второй сценарий — это, по сути, оптимистичный параллелизм, поэтому он все еще применим здесь. Они должны выдавать ошибку параллелизма, если загруженная вами запись была удалена с момента вашего последнего взаимодействия с БД. - person Asad Saeeduddin; 23.04.2015
comment
@Asad Хотя в оптимистичных версиях строки управления параллелизмом (или любого токена параллелизма) также сравниваются в операторах удаления, это все еще не применяется здесь, потому что записи соединения никогда не обновляются. Никогда не будет ситуации, когда в удалении будет отказано из-за измененного токена параллелизма. - person Gert Arnold; 23.04.2015
comment
Я думаю, что ваш аргумент основан на ложной предпосылке. Предпосылка заключается в том, что параллелизм не применяется к соединительным таблицам. Как упоминает Асад, ваш второй сценарий сам по себе является проблемой параллелизма. Ну как это не применить? Кроме того, DbUpdateException и DbUpdateConcurrencyException это разные вещи. Просто потому, что было исключение обновления, я не могу предположить, что это было из-за проблем с параллелизмом, и обрабатывать его таким образом. Во втором сценарии EF должен генерировать исключение DbUpdateConcurrencyException, которое предоставит правильную информацию в аргументе e. - person Farhad Alizadeh Noori; 23.04.2015
comment
@GertArnold На самом деле первый сценарий также является проблемой параллелизма. Дело не в том, что пользователи не могут добавлять/удалять эти записи... они могут. Проблема заключается в обработке этих исключений, которые должны иметь тип DbUpdateConcurrencyException, но это не так. - person Farhad Alizadeh Noori; 23.04.2015
comment
Предпосылка заключается в том, что оптимистичный параллелизм (с использованием токенов параллелизма для оповещения о параллельных обновлениях) не применяется. Конечно, есть параллелизм. Не всякий параллелизм является оптимистичным параллелизмом. Я вернусь к этому позже. - person Gert Arnold; 23.04.2015
comment
@GertArnold Оптимистичный параллелизм просто означает, что вы допускаете конфликт данных и проверяете его каждый раз, когда вносите изменения. Версии строк — это всего лишь один из способов проверки на конфликтность (они также используют сравнение всех полей в некоторых сценариях). В случае с соединительной таблицей вам не нужна версия строки для проверки конкуренции, но вы все равно можете иметь оптимистичный параллелизм, если вы отслеживаете количество удаленных строк и выдаете ошибку параллелизма, если оно равно 0. - person Asad Saeeduddin; 23.04.2015
comment
Во всяком случае, я думаю, что спорить о семантике здесь не слишком полезно. Ваш ответ в основном правильный, но что действительно нужно OP, так это иметь возможность определить, когда обновление не удалось из-за проблемы параллелизма, и справиться с этим. - person Asad Saeeduddin; 23.04.2015
comment
@Asad Хорошо, видимо, мы просто не согласны с определениями. Дело в том, что EF предпочитает не выдавать DbUpdateConcurrencyException, когда запись без маркера параллелизма кажется удаленной до того, как вы попытаетесь это сделать. Он просто выдает это DbUpdateException с внутренним исключением, указывающим, что сущности могли быть изменены или удалены с момента загрузки сущностей. Ага... - person Gert Arnold; 23.04.2015
comment
Итак, это действительно похоже на решение для @FarhadAlizadehNoori. Каков тип внутреннего исключения? - person Asad Saeeduddin; 23.04.2015

Хотя в таблице UserFriends нет столбца rowversion, EF по-прежнему может распознать, что причиной сбоя DbContext.SaveChanges является проблема параллелизма в случае отношения «многие ко многим». В таком случае ef генерирует исключение OptimisticConcurrencyException, скрытое с помощью DbUpdateException. Используйте следующий код, чтобы поймать его:

try
{
    context.SaveChanges();
}
catch (DbUpdateException ex)
{
    if(ex.InnerException is OptimisticConcurrencyException)
    {
        // If you are here then there was concurrency problem in many-to-many relationship
    }
}

Вы не можете настроить в настоящее время Fluent API для автоматического включения в столбец rowversion таблицы UserFriends, однако после создания миграции вы можете вручную добавить в оператор UserFriends CreateTable определение столбца rowversion:

CreateTable(
    "dbo.UserFriends",
    c => new{
        RowVersion = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
        // other columns definitions
    });

однако это не меняет поведение DbContext, когда возникает проблема параллелизма в отношениях «многие ко многим».

person mr100    schedule 22.04.2015