如何使用递归关系向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具有相同ContainerItem ,但是我将在SaveChanges()上获得一个重复的主键exception。

如果我Add一个容器Add到DbSet,那么Item也被添加到Set中吗? 我怎样才能AddOrUpdate呢?

我不确定您是否要将相关的ContainerItem一起插入数据库,或者您只想创建与现有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(); }