如何在实体框架迁移中使用多对多关系播种数据
我使用实体框架迁移(在自动迁移模式下)。一切都很好,但我有一个问题:
当我有多对多关系时,我应该如何播种数据?
例如,我有两个模型类:
public class Parcel
{
public int Id { get; set; }
public string Description { get; set; }
public double Weight { get; set; }
public virtual ICollection<BuyingItem> Items { get; set; }
}
public class BuyingItem
{
public int Id { get; set; }
public decimal Price { get; set; }
public virtual ICollection<Parcel> Parcels { get; set; }
}
我了解如何播种简单数据(对于 PaymentSystem 类)和一对多关系,但是我应该在 Seed
方法中编写什么代码来生成一些实例Parcel
和 BuyingItem
?我的意思是使用 DbContext.AddOrUpdate(),因为我不想每次运行 Update-Database 时都重复数据。
protected override void Seed(ParcelDbContext context)
{
context.AddOrUpdate(ps => ps.Id,
new PaymentSystem { Id = 1, Name = "Visa" },
new PaymentSystem { Id = 2, Name = "PayPal" },
new PaymentSystem { Id = 3, Name = "Cash" });
}
protected override void Seed(Context context)
{
base.Seed(context);
// This will create Parcel, BuyingItems and relations only once
context.AddOrUpdate(new Parcel()
{
Id = 1,
Description = "Test",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.SaveChanges();
}
此代码创建 Parcel
、BuyingItems
及其关系,但如果我需要在另一个 Parcel
中使用相同的 BuyingItem
(它们具有多对多关系),我对第二个地块重复此代码 - 它将在数据库中复制 BuyingItems
(尽管我设置了相同的 Id
)。
示例:
protected override void Seed(Context context)
{
base.Seed(context);
context.AddOrUpdate(new Parcel()
{
Id = 1,
Description = "Test",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.AddOrUpdate(new Parcel()
{
Id = 2,
Description = "Test2",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.SaveChanges();
}
如何在不同的 Parcel
中添加相同的 BuyingItem
?
I use entity framework migration (in Automatic migration mode). Everything is okay, but I have one question:
How should I seed data when I have many-to-many relationships?
For example, I have two model classes:
public class Parcel
{
public int Id { get; set; }
public string Description { get; set; }
public double Weight { get; set; }
public virtual ICollection<BuyingItem> Items { get; set; }
}
public class BuyingItem
{
public int Id { get; set; }
public decimal Price { get; set; }
public virtual ICollection<Parcel> Parcels { get; set; }
}
I understand how to seed simple data (for PaymentSystem class) and one-to-many relationships, but what code should I write in the Seed
method to generate some instances of Parcel
and BuyingItem
? I mean using DbContext.AddOrUpdate()
, because I don't want to duplicate data every time I run Update-Database
.
protected override void Seed(ParcelDbContext context)
{
context.AddOrUpdate(ps => ps.Id,
new PaymentSystem { Id = 1, Name = "Visa" },
new PaymentSystem { Id = 2, Name = "PayPal" },
new PaymentSystem { Id = 3, Name = "Cash" });
}
protected override void Seed(Context context)
{
base.Seed(context);
// This will create Parcel, BuyingItems and relations only once
context.AddOrUpdate(new Parcel()
{
Id = 1,
Description = "Test",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.SaveChanges();
}
This code creates Parcel
, BuyingItems
and their relationship, but if I need the same BuyingItem
in another Parcel
(they have a many-to-many relationship) and I repeat this code for the second parcel - it will duplicate BuyingItems
in the database (though I set the same Id
s).
Example:
protected override void Seed(Context context)
{
base.Seed(context);
context.AddOrUpdate(new Parcel()
{
Id = 1,
Description = "Test",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.AddOrUpdate(new Parcel()
{
Id = 2,
Description = "Test2",
Items = new List<BuyingItem>
{
new BuyingItem() { Id = 1, Price = 10M },
new BuyingItem() { Id = 2, Price = 20M }
}
});
context.SaveChanges();
}
How can I add the same BuyingItem
in different Parcel
s?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
更新答案
首先,我们创建一个复合主键(由parcel id 和item id 组成)来消除重复。在
DbContext
类中添加以下方法:然后实现
Seed
方法,如下所示:这里我们确保通过调用
在数据库中创建/更新实体
。之后,我们使用Seed
方法中的 context.SaveChanges()context
检索所需的包裹和购买项目对象。此后,我们使用Parcel
对象上的Items
属性(这是一个集合)来根据需要添加BuyingItem
。请注意,无论我们使用同一个项目对象调用
Add
方法多少次,最终都不会出现主键冲突。这是因为 EF 内部使用HashSet
来管理Parcel.Items
集合。HashSet
本质上不允许您添加重复的项目。此外,如果您以某种方式设法规避此 EF 行为(正如我在示例中演示的那样),我们的主键将不会让重复项进入。
正确使用
AddOrUpdate
当您使用典型的 Id 字段(int、 Identity) 作为带有
AddOrUpdate
方法的标识符表达式,您应该谨慎行事。在这种情况下,如果您手动从 Parcel 表中删除一行,则每次运行 Seed 方法时最终都会创建重复项(即使使用我上面提供的更新的
Seed
方法) 。考虑以下代码,
从技术上讲(考虑此处的代理 ID),行是唯一的,但从最终用户的角度来看,它们是重复的。
这里真正的解决方案是使用
Description
字段作为标识符表达式。将此属性添加到Parcel
类的Description
属性中以使其唯一:[MaxLength(255), Index(IsUnique=true)]
。更新Seed
方法中的以下代码片段:注意,我没有使用
Id
字段,因为 EF 在插入行时会忽略它。我们使用Description
来检索正确的地块对象,无论Id
值是什么。旧答案
我想在这里添加一些观察结果:
如果 Id 列是数据库生成的字段,那么使用 Id 可能不会有任何好处。 EF 将忽略它。
当
Seed
方法运行一次时,此方法似乎工作正常。它不会创建任何重复项,但是,如果您第二次运行它(我们大多数人必须经常这样做),它可能会注入重复项。就我而言,确实如此。Tom Dykstra 的本教程 向我展示了正确的方法。它之所以有效,是因为我们不认为任何事情都是理所当然的。我们不指定 ID。相反,我们通过已知的唯一键查询上下文并向它们添加相关实体(再次通过查询上下文获取)。它对我来说就像一个魅力。
Updated Answer
First of all, let's create a composite primary key (consisting of parcel id and item id) to eliminate duplicates. Add the following method in the
DbContext
class:Then implement the
Seed
method like so:Here we make sure that the entities are created/updated in the database by calling
context.SaveChanges()
within theSeed
method. After that, we retrieve the required parcel and buying item objects usingcontext
. Thereafter we use theItems
property (which is a collection) on theParcel
objects to addBuyingItem
as we please.Please note, no matter how many times we call the
Add
method using the same item object, we don't end up with primary key violation. That is because EF internally usesHashSet<T>
to manage theParcel.Items
collection. AHashSet<Item>
, by its nature, won't let you add duplicate items.Moreover, if you somehow manage to circumvent this EF behavior as I have demonstrated in the example, our primary key won't let the duplicates in.
Using
AddOrUpdate
ProperlyWhen you use a typical Id field (int, identity) as an identifier expression with
AddOrUpdate
method, you should exercise caution.In this instance, if you manually delete one of the rows from the Parcel table, you'll end up creating duplicates every time you run the Seed method (even with the updated
Seed
method I have provided above).Consider the following code,
Technically (considering the surrogate Id here), the rows are unique, but from the end-user point of view, they are duplicates.
The true solution here is to use the
Description
field as an identifier expression. Add this attribute to theDescription
property of theParcel
class to make it unique:[MaxLength(255), Index(IsUnique=true)]
. Update the following snippets in theSeed
method:Note, I'm not using the
Id
field as EF is going to ignore it while inserting rows. And we are usingDescription
to retrieve the correct parcel object, no matter whatId
value is.Old Answer
I would like to add a few observations here:
Using Id is probably not going to do any good if the Id column is a database generated field. EF is going to ignore it.
This method seems to be working fine when the
Seed
method is run once. It won't create any duplicates, however, if you run it for a second time (and most of us have to do that often), it may inject duplicates. In my case it did.This tutorial by Tom Dykstra showed me the right way of doing it. It works because we don't take anything for granted. We don't specify IDs. Instead, we query the context by known unique keys and add related entities (which again are acquired by querying context) to them. It worked like a charm in my case.
您必须以与在任何 EF 代码中构建多对多关系相同的方式填充多对多关系:
指定将在数据库中使用的
Id
至关重要,否则每次Update -数据库
将创建新记录。AddOrUpdate
不支持以任何方式更改关系,因此您无法使用它在下次迁移中添加或删除关系。如果您需要它,则必须通过使用BuyingItems
加载Parcel
并在导航集合上调用Remove
或Add
来手动删除关系打破或添加新关系。You must fill many-to-many relation in the same way as you build many-to-many relation in any EF code:
Specifying
Id
which will be used in database is crucial otherwise eachUpdate-Database
will create new records.AddOrUpdate
doesn't support changing relations in any way so you cannot use it to add or remove relations in next migration. If you need it you must manually remove relation by loadingParcel
withBuyingItems
and callingRemove
orAdd
on navigation collection to break or add new relation.好的。我明白在这种情况下我应该如何:
数据库中没有重复项。
谢谢你,拉迪斯拉夫,你给了我一个正确的向量来找到我的任务的解决方案。
Ok. I understand how I should be in that situation:
There are no duplicates in database.
Thank you, Ladislav, you gave me a right vector to find a solution for my task.