Обновленный ответ
Убедитесь, что вы читаете раздел «Использование AddOrUpdate должным образом» ниже для полного ответа.
Прежде всего, давайте создадим составной первичный ключ (состоящий из идентификатора посылки и предмета) для устранения дубликатов.Добавьте следующий метод в класс DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Parcel>()
.HasMany(p => p.Items)
.WithMany(r => r.Parcels)
.Map(m =>
{
m.ToTable("ParcelItems");
m.MapLeftKey("ParcelId");
m.MapRightKey("BuyingItemId");
});
}
Затем реализуйте метод Seed следующим образом:
protected override void Seed(Context context)
{
context.Parcels.AddOrUpdate(
p => p.Id,
new Parcel { Id = 1, Description = "Parcel 1", Weight = 1.0 },
new Parcel { Id = 2, Description = "Parcel 2", Weight = 2.0 },
new Parcel { Id = 3, Description = "Parcel 3", Weight = 3.0 }
);
context.BuyingItems.AddOrUpdate(
b => b.Id,
new BuyingItem { Id = 1, Price = 10m },
new BuyingItem { Id = 2, Price = 20m }
);
// Make sure that the above entities are created in the database
context.SaveChanges();
var p1 = context.Parcels.Find(1);
// Uncomment the following line if you are not using lazy loading.
//context.Entry(p1).Collection(p => p.Items).Load();
var p2 = context.Parcels.Find(2);
// Uncomment the following line if you are not using lazy loading.
//context.Entry(p2).Collection(p => p.Items).Load();
var i1 = context.BuyingItems.Find(1);
var i2 = context.BuyingItems.Find(2);
p1.Items.Add(i1);
p1.Items.Add(i2);
// Uncomment to test whether this fails or not, it will work, and guess what, no duplicates!!!
//p1.Items.Add(i1);
//p1.Items.Add(i1);
//p1.Items.Add(i1);
//p1.Items.Add(i1);
//p1.Items.Add(i1);
p2.Items.Add(i1);
p2.Items.Add(i2);
// The following WON'T work, since we're assigning a new collection, it'll try to insert duplicate values only to fail.
//p1.Items = new[] { i1, i2 };
//p2.Items = new[] { i2 };
}
Здесь мы гарантируем, что сущности создаются \ обновляются в базе данных с помощьювызов context.SaveChanges()
в методе Seed
.После этого мы получаем необходимую посылку и покупаем предметы, используя context
.После этого мы используем свойство Items
(которое является коллекцией) для Parcel
объектов, чтобы добавить BuyingItem
, как нам угодно.
Обратите внимание, независимо от того, сколько раз мы вызываем метод Add
, используятот же объект элемента, мы не заканчиваем с нарушением первичного ключа.Это связано с тем, что EF внутренне использует HashSet<T>
для управления Parcel.Items
коллекцией.HashSet<Item>
по своей природе не позволит вам добавлять дубликаты.
Более того, если вам как-то удастся обойти это поведение EF, как я продемонстрировал в примере, наш первичный ключ не будетвпишите дубликаты.
Правильное использование AddOrUpdate
При использовании типичного поля Id (int, identity) в качестве выражения идентификатора с методом AddOrUpdate
следует соблюдать осторожность.
В этом случае, если вы вручную удалите одну из строк из таблицы Parcel, вы в конечном итоге создадите дубликаты при каждом запуске метода Seed (даже с обновленным методом Seed
, который я предоставил выше.).
Рассмотрим следующий код:
context.Parcels.AddOrUpdate(
p => p.Id,
new Parcel { Id = 1, Description = "Parcel 1", Weight = 1.0 },
new Parcel { Id = 2, Description = "Parcel 1", Weight = 1.0 },
new Parcel { Id = 3, Description = "Parcel 1", Weight = 1.0 }
);
Технически (с учетом суррогатного идентификатора здесь) строки уникальны, но с точки зрения конечного пользователя они являются дубликатами.
Истинным решением здесь является использование поля Description
в качестве идентификатора выражения.Добавьте этот атрибут в свойство Description
класса Parcel
, чтобы сделать его уникальным, [MaxLength(255), Index(IsUnique=true)]
.Обновите следующие фрагменты в методе Seed
:
context.Parcels.AddOrUpdate(
p => p.Description,
new Parcel { Description = "Parcel 1", Weight = 1.0 },
new Parcel { Description = "Parcel 2", Weight = 2.0 },
new Parcel { Description = "Parcel 3", Weight = 3.0 }
);
// Make sure that the above entities are created in the database
context.SaveChanges();
var p1 = context.Parcels.Single(p => p.Description == "Parcel 1");
Обратите внимание, я не использую поле Id
, так как EF будет игнорировать его при вставке строк.И мы используем Description
для получения правильного объекта посылки, независимо от значения Id
.
Старый ответ
Я бы хотелДобавьте сюда несколько замечаний:
Использование Id, вероятно, не принесет пользы, если столбец Id является полем, созданным базой данных.EF будет игнорировать его.
Этот метод работает нормально, когда метод Seed запускается один раз.Он не будет создавать дубликаты, однако, если вы запустите его во второй раз (и большинство из нас должны делать это часто), он может ввести дубликаты.В моем случае это так.
Этот урок Тома Дайкстры показал мне правильный способ сделать это.Это работает, потому что мы ничего не принимаем как должное.Мы не указываем идентификаторы.Вместо этого мы запрашиваем контекст по известным уникальным ключам и добавляем к ним связанные сущности (которые снова получают путем запроса контекста).В моем случае это сработало как шарм.