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 Slug
s.
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();