如何使用递归关系向DbContext添加实体,同时避免重复添加?
我正在使用Entity Framework 4.4,我有一个像这样的One-To-Many关系模型:
class Item { public string keyPart1 { get; set; } public string keyPart2 { get; set; } public virtual Container container { get; set; } public string ContainerId { get; set; } } // Idea is that many Items will be assigned to a container class Container { public string ContainerId { get; set; } private ICollection _Items; public virtual ICollection As { get { return _Items ?? (_Items = new HashSet()); } protected set { _Items = value; } } }
现在,这是DbContext:
public class StorageContext : DbContext { public DbSet Items { get; set; } public DbSet Buckets { get; set; } public override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity().HasKey( i => new { i.keyPart1, i.keyPart2 } ).HasRequired( i => i.Container ); } }
现在,假设我有N个Item实例。 每个Item都属于一个容器,它包含多个项目实例,每个实例都属于一个容器,因此模型会无休止地进行递归。
我想循环遍历我当前的Item实例列表并将每个实例添加到db上下文:
foreach (var i in LocalItemList) { IDbSetExtensions.AddOrUpdate(db.Items, i); } dbContext.SaveChanges();
我无法弄清楚的问题是如何告诉上下文AddOrUpdate
Container
以便我不会得到主键重复exception。 在某些时候,我们将遇到一个与另一个Container
具有相同Container
的Item
,但是我将在SaveChanges()
上获得一个重复的主键exception。
如果我Add
一个容器Add
到DbSet,那么Item
也被添加到Set中吗? 我怎样才能AddOrUpdate
呢?
我不确定您是否要将相关的Container
与Item
一起插入数据库,或者您只想创建与现有Container
的关系。 数据库和对象图中不存在Container
实体的可能情况,每个键只有一个Container
实例 (允许多个对这些实例的引用 )应该不是问题,而是一个简单的代码,如…
foreach (var i in LocalItemList) { dbContext.Items.Add(i); } dbContext.SaveChanges();
……实际应该毫无例外地工作。 因此,您可能会遇到以下两种情况之一来解释主键约束违规:
-
Container
实体已经存在于数据库中,并且在对象图中,每个键只有一个Container
实例 (再次允许对这些实例进行多次引用 )。 这是一个简单的案例,您可以使用以下方法解决它:foreach (var i in LocalItemList) { dbContext.Containers.Attach(i.Container); dbContext.Items.Add(i); } dbContext.SaveChanges();
如果你有同一个密钥的多个
Container
实例,这将无法工作,并抛出一个“…具有相同密钥的对象已经存在于ObjectContext …” (或类似的)exception中。 如果Container
在数据库中尚不存在,它也将无法工作(那么你可能会遇到外键约束违规)。 -
如果在对象图中有多个具有相同键的
Container
对象实例,则必须在每个键上使用一个唯一实例替换重复项,然后才能将实体附加或添加到上下文中。 为了简化这个过程,我首先删除循环引用,然后使用Local
集合来确定是否已经附加了具有相同键的Container
:foreach (var i in LocalItemList) { i.Container.Items = null; var attachedContainer = dbContext.Containers.Local .SingleOrDefault(c => c.ContainerId == i.Container.ContainerId); if (attachedContainer != null) i.Container = attachedContainer; else dbContext.Containers.Attach(i.Container); // or dbContext.Containers.Add(i.Container); // depending on if you want to insert the Container or not dbContext.Items.Add(i); } dbContext.SaveChanges();
它似乎工作正常,只要你让EntityFramework知道任何pre-exisitng容器。
例如,此测试在空数据库上运行。 两个项目都在同一个Container中,但EF只插入一次Container。
[TestMethod] public void Populate() { const string conStr = "Data Source=(local);Initial Catalog=EfExperiment1; Integrated Security=True"; var context = new MyDbContext(conStr); var container = new Container { ContainerId = "12345" }; var item1 = new Item { keyPart1 = "i1k1", keyPart2 = "i1k2", Container = container }; var item2 = new Item { keyPart1 = "i2k1", keyPart2 = "i2k2", Container = container }; context.Items.Add(item1); context.Items.Add(item2); context.SaveChanges(); }
当容器已存在时运行此测试。 通过尝试从db读取容器,我们使EF知道任何现有实例。
[TestMethod] public void PopulateSomeMore() { const string conStr = "Data Source=(local);Initial Catalog=EfExperiment1; Integrated Security=True"; var context = new MyDbContext(conStr); var container = context.Buckets.FirstOrDefault(c => c.ContainerId == "12345") ?? new Container { ContainerId = "12345" }; var item3 = new Item { keyPart1 = "i3k1", keyPart2 = "i3k2", Container = container }; var item4 = new Item { keyPart1 = "i4k1", keyPart2 = "i4k2", Container = container }; context.Items.Add(item3); context.Items.Add(item4); context.SaveChanges(); }
此测试不会加载现有容器,因此EF认为它需要插入并因重复键错误而失败。
[TestMethod] public void PopulateSomeMoreAgain() { const string conStr = "Data Source=(local);Initial Catalog=EfExperiment1; Integrated Security=True"; var context = new MyDbContext(conStr); var container = new Container { ContainerId = "12345" }; var item5 = new Item { keyPart1 = "i5k1", keyPart2 = "i5k2", Container = container }; var item6 = new Item { keyPart1 = "i6k1", keyPart2 = "i6k2", Container = container }; context.Items.Add(item5); context.Items.Add(item6); context.SaveChanges(); }