AddOrUpdate работает не так, как ожидалось, и создает дубликаты

Я использую установку EF5 на основе Code-First DBContext.

В DbMigrationsConfiguration.Seed я пытаюсь заполнить БД фиктивными данными по умолчанию. Для выполнения этой задачи я использую метод DbSet.AddOrUpdate.

Самый простой код для иллюстрации моей цели:

j = 0;

var cities = new[]
    {
        "Berlin",
        "Vienna",
        "London",
        "Bristol",
        "Rome",
        "Stockholm",
        "Oslo",
        "Helsinki",
        "Amsterdam",
        "Dublin"
    };
var cityObjects = new City[cities.Length];


foreach (string c in cities)
{
    int id = r.NextDouble() > 0.5 ? 0 : 1;
    var city = new City
        {
            Id = j,
            Name = c,
            Slug = c.ToLowerInvariant(),
            Region = regions[id],
            RegionId = regions[id].Id,
            Reviewed = true
        };
    context.CitySet.AddOrUpdate(cc => cc.Id, city);
    cityObjects[j] = city;
    j++;
}

Я пытался использовать/опускать поле Id, а также использовать свойство Id/Slug в качестве селектора обновлений.

при запуске Update-Database поле Id игнорируется, и значение генерируется автоматически SQL Server, а БД заполняется дубликатами; Селектор Slug допускает дублирование и при последующих запусках создает исключения (Sequence contains more than one element).

Предназначен ли метод AddOrUpdate для такой работы? Должен ли я выполнять upsert вручную?


person berezovskyi    schedule 08.12.2012    source источник


Ответы (1)


Во-первых (пока нет ответа), AddOrUpdate можно вызывать с массивом новых объектов, поэтому вы можете просто создать массив типа City[] и вызвать context.CitySet.AddOrUpdate(cc => cc.Id, cityArray); один раз.

(отредактировано)

Во-вторых, AddOrUpdate использует выражение идентификатора (cc => cc.Id) для поиска городов с тем же Id, что и в массиве. Эти города будут обновлены. Другие города в массиве будут вставлены, но их значения Id будут сгенерированы базой данных, поскольку Id — это столбец идентификаторов. Его нельзя установить оператором вставки. (Если вы не включили вставку личных данных). Поэтому при использовании AddOrUpdate для таблиц со столбцами идентификаторов вам следует найти другой способ идентификации записей, поскольку значения идентификаторов существующих записей непредсказуемы.

В вашем случае вы использовали Slug в качестве идентификатора для AddOrUpdate, который должен быть уникальным (согласно вашему комментарию). Мне непонятно, почему это не обновляет существующие записи с соответствующими Slugs.

Я провел небольшой тест: добавил или обновил сущность с идентификатором (iedntity) и уникальным именем:

var n = new Product { ProductID = 999, ProductName = "Prod1", UnitPrice = 1.25 };
Products.AddOrUpdate(p => p.ProductName, n);
SaveChanges();

Когда «Prod1» еще нет, он вставляется (игнорируя идентификатор 999).
Если он есть и UnitPrice отличается, он обновляется.

Глядя на испускаемые запросы, я вижу, что EF ищет уникальную запись по имени:

SELECT TOP (2) 
[Extent1].[ProductID] AS [ProductID], 
[Extent1].[ProductName] AS [ProductName], 
[Extent1].[UnitPrice] AS [UnitPrice]
FROM [dbo].[Products] AS [Extent1]
WHERE N'Prod1' = [Extent1].[ProductName]

И далее (когда совпадение найдено и UnitPrice отличается)

update [dbo].[Products]
set [UnitPrice] = 1.26
where ([ProductID] = 15)

Это показывает, что EF нашел одну запись и теперь использует ключевое поле для обновления.

Я надеюсь, что этот пример прольет свет на вашу ситуацию. Возможно, вам также следует следить за операторами sql и смотреть, не произойдет ли там что-нибудь неожиданное.

person Gert Arnold    schedule 08.12.2012
comment
Герт, спасибо за попытку помочь! На самом деле, City был присвоен id=j, который находится в диапазоне от 0 до 9. Вас просто обманули похожими именами переменных. Далее я уже упомянул об эффекте использования Name (в моем случае Slug, т.к. он действительно уникален) — он пропускает дубликат в БД. Передача всех объектов в виде массива не дала никакого прогресса. - person berezovskyi; 09.12.2012
comment
Спасибо за обновление вашего ответа. Я больше не могу проверить это. Если кто-то из сообщества напишет под вашим ответом, что это ему помогло, я отмечу ваш ответ как принятый. Извините, что это заняло так много времени. - person berezovskyi; 27.05.2014
comment
Есть идеи, как выглядело бы выражение идентификатора, если бы для уникальной идентификации записи требовалось два поля? Я думаю, что p => p.ProductName, p.CategoryName (но, конечно, это не работает). - person Jarvis; 19.07.2014
comment
p => new { p.ProductName, p.CategoryName } @Джарвис - person Gert Arnold; 20.07.2014
comment
В моем случае этот код потерпел неудачу при использовании context.Set<TEntity>().AddOrUpdate(...), вы должны ссылаться на фактический DBSet. - person Bron Davies; 17.11.2016
comment
@BronDavies context.Set<TEntity>() на самом деле DbSet. Если здесь что-то пойдет не так, это должно быть что-то другое, что вызвало это. - person Gert Arnold; 18.11.2016