Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Я установил небольшой тест: добавьте или обновите объект с идентификатором (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 и видеть, произошло ли что-то неожиданное.

Ответ 2

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();