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

C# 如何使用递归关系向DbContext添加实体,同时避免重复添加?,c#,entity-framework,entity-framework-4.3,C#,Entity Framework,Entity Framework 4.3,我使用的是实体框架4.4,我有一个一对多关系模型,如下所示: 类项{ 公共字符串keyPart1{get;set;} 公共字符串keyPart2{get;set;} 公共虚拟容器{get;set;} 公共字符串容器ID{get;set;} } //其思想是将许多项目分配给一个容器 类容器{ 公共字符串容器ID{get;set;} 私人ICollection项目; 公共虚拟ICollection As { 获取{return\u Items???(\u Items=new HashSet());}

我使用的是实体框架4.4,我有一个一对多关系模型,如下所示:

类项{
公共字符串keyPart1{get;set;}
公共字符串keyPart2{get;set;}
公共虚拟容器{get;set;}
公共字符串容器ID{get;set;}
}
//其思想是将许多项目分配给一个容器
类容器{
公共字符串容器ID{get;set;}
私人ICollection项目;
公共虚拟ICollection As
{
获取{return\u Items???(\u Items=new HashSet());}
受保护集{u 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 );
    }
}
公共类StorageContext:DbContext
{
公共数据库集项{get;set;}
公共数据库集存储桶{get;set;}
模型创建时的公共覆盖无效(DbModelBuilder modelBuilder)
{
基于模型创建(modelBuilder);
modelBuilder.Entity().HasKey(i=>new{i.keyPart1,i.keyPart2}).HasRequired(i=>i.Container);
}
}
现在,假设我有N个Item实例。每个项目都属于一个容器,该容器包含多个项目实例,每个项目实例都属于一个容器,因此模型会无休止地递归

然后,我想循环查看当前的项目实例列表,并将每个实例添加到db上下文中:

foreach (var i in LocalItemList)
{ 
   IDbSetExtensions.AddOrUpdate<Item>(db.Items, i);
}
dbContext.SaveChanges();
foreach(LocalItemList中的变量i)
{ 
IDbSetExtensions.AddOrUpdate(db.Items,i);
}
dbContext.SaveChanges();
我不明白的问题是如何告诉上下文添加或更新
容器
,这样我就不会得到主键重复异常。在某个时刻,我们会遇到一个
,该项与另一个项具有相同的
容器
,但在
SaveChanges()
上会出现重复的主键异常


如果我
Add
一个容器到DbSet,那么
项是否也添加到集合中?我如何才能将其改为
添加或更新?

我不确定您是否要将相关的
容器
一起插入数据库,或者您是否只想与现有的
容器
建立关系。数据库中不存在
容器
实体,并且在对象图中,每个键只有一个
容器
实例(允许对这些实例进行多次引用)的可能情况应该根本不成问题,简单的代码如

    foreach (var i in LocalItemList)
    {
        dbContext.Items.Add(i);
    }
    dbContext.SaveChanges();
…应该毫无例外地起作用。因此,您可能有以下两种情况之一可以解释主键约束冲突:

  • 数据库中已经存在
    容器
    实体,在对象图中,每个键只有一个
    容器
    实例(再次允许对这些实例进行多次引用)。这是一个简单的案例,您可以使用以下方法解决:

    foreach (var i in LocalItemList)
    {
        dbContext.Containers.Attach(i.Container);
        dbContext.Items.Add(i);
    }
    dbContext.SaveChanges();
    
    如果同一个键有多个
    容器
    实例,这将不起作用,并引发“…ObjectContext中已存在具有相同键的对象…”(或类似)异常。如果
    容器
    在数据库中还不存在,它也将不起作用(您可能会遇到外键约束冲突)

  • 如果在对象图中有多个具有相同键的
    容器
    对象实例,则在将实体附加或添加到上下文之前,必须将重复项替换为每个键一个唯一的实例。为了简化此过程,我将首先删除循环引用,然后使用
    Local
    集合确定是否已附加具有相同密钥的
    容器

    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知道任何预先存在的容器,它似乎工作正常

例如,此测试在空数据库上运行。两个项目都在同一个容器中,但EF只插入容器一次

    [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();
    }