如何使用递归关系向DbContext添加实体,同时避免重复添加?

时间:2013-03-06 19:09:14

标签: c# entity-framework entity-framework-4.3

我正在使用Entity Framework 4.4,我有一个像这样的一对多关系模型:

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<Item> _Items;
   public virtual ICollection<Item> As
   {
      get { return _Items ?? (_Items = new HashSet<A>()); }
      protected set { _Items = value; }
   }
}

现在,这是DbContext:

public class StorageContext : DbContext
{
    public DbSet<Item> Items { get; set; }
    public DbSet<Bucket> Buckets { get; set; }

    public override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Item>().HasKey( i => new { i.keyPart1, i.keyPart2 } ).HasRequired( i => i.Container );
    }
}

现在,假设我有N个Item实例。每个Item都属于一个容器,它包含多个项目实例,每个实例都属于一个容器,因此模型无休止地进行递归。

我想循环浏览我当前的Item实例列表,并将每个实例添加到db上下文:

foreach (var i in LocalItemList)
{ 
   IDbSetExtensions.AddOrUpdate<Item>(db.Items, i);
}
dbContext.SaveChanges();

我无法弄清楚的问题是如何告诉AddOrUpdate Container的上下文,以便我不会获得主键重复异常。在某些时候,我们会遇到与Item具有相同Container的{​​{1}},但我会在SaveChanges()上获得重复的主键例外。

如果Add是DbSet的容器​​,那么Item是否也被添加到了Set中?我怎样才能改为AddOrUpdate

2 个答案:

答案 0 :(得分:2)

我不确定您是否要将相关的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中已经存在一个具有相同密钥的对象......”(或类似)例外。如果数据库中尚不存在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();
    

答案 1 :(得分:1)

只要你让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();
    }