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

Я использую первую в коде настройку на основе DBContext EF5.

на 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++;
}

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

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

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

2 ответов


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

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

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

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

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

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

когда "Prod1" еще нет, он вставляется (игнорируя Id 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 и посмотреть, не произойдет ли там чего-нибудь неожиданного.


var paidOutType = new List<PaidOutType>
                {
                    new PaidOutType { PaidOutTypeID = 1, Code = "001", Description = "PAID OUT 1", PType = "1", Amount = 0, IsSalesSummery = true,DayFrom=1,DayTo=31 },
                    new PaidOutType { PaidOutTypeID = 2, Code = "002", Description = "PAID OUT 2", PType = "1", Amount = 0, IsSalesSummery = true,DayFrom=1,DayTo=31 },
                    new PaidOutType { PaidOutTypeID = 3, Code = "002", Description = "PAID OUT 3", PType = "1", Amount = 0, IsSalesSummery = true,DayFrom=1,DayTo=31 },
                };
                paidOutType.ForEach(u => smartPOSContext.PaidOutType.AddOrUpdate(u));
                smartPOSContext.SaveChanges();