1

私は Entity Framework 4.4 を使用しており、次のような 1 対多の関係モデルがあります。

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 インスタンスのリストを循環し、それぞれを db コンテキストに追加します。

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

私が理解できない問題は、主キーの重複例外が発生しないようにコンテキストを伝える方法AddOrUpdateですContainer。ある時点で、別Itemの と同じContainerである に遭遇しますが、 で主キーの重複例外が発生しSaveChanges()ます。

AddDbSet のコンテナである場合Item、セットにも s が追加されますか? 代わりにどうすればそれを作ることができAddOrUpdateますか?

4

2 に答える 2

2

Container関連する を とともにデータベースに挿入したいItemのか、それとも既存Containerの との関係を作成したいだけなのかわかりません。エンティティがデータベースに存在せず、オブジェクト グラフにキーごとに 1 つのインスタンスContainerしかない(これらのインスタンスへの複数の参照が許可されている)という可能性のあるケースは、まったく問題ではなく、次のような単純なコードです...Container

    foreach (var i in LocalItemList)
    {
        dbContext.Items.Add(i);
    }
    dbContext.SaveChanges();

...実際には例外なく動作するはずです。したがって、主キー制約違反を説明する次の 2 つの状況のいずれかが考えられます。

  • エンティティはデータベースに既に存在し、オブジェクト グラフにはキーごとに 1 つのインスタンスContainerしかありません(これらのインスタンスへの複数の参照が再び許可されます)。これは簡単なケースであり、次を使用して解決できます。Container

    foreach (var i in LocalItemList)
    {
        dbContext.Containers.Attach(i.Container);
        dbContext.Items.Add(i);
    }
    dbContext.SaveChanges();
    

    同じキーに対して複数のContainerインスタンスがある場合、これは機能せず、「...同じキーを持つオブジェクトが ObjectContext に既に存在します...」 (または同様の) 例外がスローされます。s がまだデータベースに存在しない場合も機能Containerしません (その場合、おそらく外部キー制約違反が発生します)。

  • Containerオブジェクト グラフに同じキーを持つオブジェクト インスタンスが複数ある場合は、エンティティをコンテキストにアタッチまたは追加する前に、重複をキーごとに 1 つの一意のインスタンスに置き換える必要があります。このプロセスを簡単にするために、最初に循環参照を削除してから、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();
    
于 2013-03-06T19:55:53.727 に答える
1

EntityFramework に既存のコンテナーを認識させる限り、問題なく動作するようです。

たとえば、このテストは空のデータベースで実行されます。両方のアイテムは同じコンテナーにありますが、EF はコンテナーを 1 回だけ挿入します。

    [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 に答える