1

我正在使用 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 实例,每个实例都属于一个容器,因此模型会无限递归。

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

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

我无法弄清楚的问题是如何告诉上下文,AddOrUpdate这样Container我就不会得到主键重复异常。在某些时候,我们会遇到一个与另一个Item相同Container的,但我会在SaveChanges().

如果我Add是 DbSet 的容器,是否也将Items 添加到 Set 中?我怎样才能做到这一点AddOrUpdate

4

2 回答 2

2

我不确定您是否要将相关的 s 与Containers 一起插入到数据库中Item,或者您是否只想创建与现有Containers 的关系。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如果数据库中尚不存在 s ,它也将不起作用(那时您可能会违反外键约束)。

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

    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();
    
于 2013-03-06T19:55:53.727 回答
1

只要您让 EntityFramework 知道任何预先存在的容器,它似乎就可以正常工作。

例如,此测试在空数据库上运行。这两个项目都在同一个 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();
    }

此测试在容器已存在时运行。通过尝试从数据库中读取容器,我们让 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();
    }
于 2013-03-06T21:42:56.340 回答