Dmi*_*kov 18 entity-framework ef-code-first ef-migrations
我使用实体框架迁移(在自动迁移模式下).一切都很好,但我有一个问题:当我有多对多的关系时,我应该如何播种数据.例如,我有两个模型类:
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; }
}
Run Code Online (Sandbox Code Playgroud)
我理解如何为简单数据(对于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" });
}
Run Code Online (Sandbox Code Playgroud)
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();
}
Run Code Online (Sandbox Code Playgroud)
是.这段代码创建了Parcel,BuyingItems和relation,但是如果我在其他Parcel中需要相同的BuyingItem(它们有多对多的关系),如果我为第二个parcel重复这个代码 - 它将在数据库中复制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();
}
Run Code Online (Sandbox Code Playgroud)
如何在不同的包中添加相同的BuyingItem?
Lad*_*nka 20
您必须以与在任何EF代码中构建多对多关系相同的方式填充多对多关系:
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();
}
Run Code Online (Sandbox Code Playgroud)
指定Id
将在数据库中使用哪个是至关重要的,否则每个都Update-Database
将创建新记录.
AddOrUpdate
不支持以任何方式更改关系,因此您无法在下次迁移中使用它来添加或删除关系.如果你需要它,你必须手动通过装载删除的关系Parcel
与BuyingItems
和调用Remove
或Add
在导航集打破或添加新的关系.
Rav*_*tel 19
请务必阅读下面的"正确使用AddOrUpdate"部分以获得完整答案.
首先,让我们创建一个复合主键(由parcel id和item id组成)以消除重复.在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");
});
}
Run Code Online (Sandbox Code Playgroud)
然后像这样实现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 };
}
Run Code Online (Sandbox Code Playgroud)
在这里,我们确保通过DbContext
在Seed
方法中调用在数据库中创建\更新实体.之后,我们使用检索所需的包裹和购买物品对象context.SaveChanges()
.此后,我们使用对象的Seed
属性(这是一个集合)context
来添加Items
.
请注意,无论我们Parcel
使用相同的项目对象调用该方法多少次,我们最终都不会遇到主键违规.这是因为EF内部用于BuyingItem
管理Add
集合.A HashSet<T>
,就其性质而言,不会让您添加重复的项目.
此外,如果你以某种方式管理这个EF行为,就像我在示例中演示的那样,我们的主键不会让重复的内容.
当您使用典型的Id字段(int,identity)作为带Parcel.Items
方法的标识符表达式时,您应该谨慎行事.
在这种情况下,如果您手动从Parcel表中删除其中一行,则每次运行Seed方法时都会创建重复项(即使使用HashSet<Item>
上面提供的更新方法).
考虑以下代码,
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 }
);
Run Code Online (Sandbox Code Playgroud)
从技术上讲(考虑到这里的代理ID),行是唯一的,但从最终用户的角度来看,它们是重复的.
这里的真正解决方案是use AddOrUpdate
field作为标识符表达式 将此属性添加到类的AddOrUpdate
属性Seed
以使其唯一Description
.更新Description
方法中的以下片段:
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");
Run Code Online (Sandbox Code Playgroud)
注意,我没有使用该Parcel
字段,因为EF在插入行时会忽略它.我们正在使用它[MaxLength(255), Index(IsUnique=true)]
来检索正确的宗地对象,无论它是什么Seed
价值.
老答案
我想在这里补充几点意见:
如果Id列是数据库生成字段,则使用Id可能不会有任何好处.EF将忽略它.
当Seed方法运行一次时,此方法似乎工作正常.它不会创建任何重复项,但是,如果您第二次运行它(我们大多数人必须经常这样做),它可能会注入重复项.就我而言,确实如此.
Tom Dykstra的这篇教程向我展示了正确的做法.这是有效的,因为我们不认为任何理所当然.我们不指定ID.相反,我们通过已知的唯一键查询上下文,并将相关实体(通过查询上下文再次获取)添加到它们.在我的案例中,它就像一个魅力.