2011-11-01 20 views
27

新しいタイプの製品を作成し、その製品にいくつかの成分を加えることができるアプリケーションがあります。製品と成分は両方ともデータベースに保存されたエンティティです。製品エンティティは、成分エンティティの集合を有する。エンティティ4.1新しい子エンティティを使用して既存の親エンティティを更新する

(簡易版)

public class Product 
    Public Sub New() 
    Me.Ingredients = New List(Of Ingredient)() 
    End Sub 

    Property Ingredients as ICollection(Of Ingredient) 
end class 

私が初めて製品を保存、すべてがうまく行く:私はちょうどコンテキストに追加し、SaveChangesメソッドを呼び出します。

myDataContext.Products.Add(product) 
myDataContext.SaveChanges() 

製品(親)と成分(子供)は、互いに保存され、リンクされています。すべては順調です。

しかし、既存の製品に成分を追加/除去すると、問題が発生し始めます。私はまず、製品エンティティの既存の成分のコレクションをクリアしてから、成分の更新されたリストをもう一度追加します(私は成分を再使用しないで瞬間を追加します)。次に、製品エンティティの状態を変更してsavechangesを呼び出します。ただし、状態Iを変更すると、例外「、同じキーを持つオブジェクトがすでにObjectStateManagerに存在します」を取得します。 「いくつかは、」私は問題はすべての成分は、(彼らはまだ追加されていないとして)0の主キーを持っていて、親エンティティの状態を変更(製品ということであることを考え出し検索した後

myDataContext.Entry(product).State = EntityState.Modified 

)、すべての子エンティティ(成分)がキーが0であるコンテキストにアタッチされているため、キーがもはや一意ではないため、問題が発生します。

私は解決策を探していますが、この問題を解決する方法を見つけることはできません。状態を変更する前にコンテキストに要素を追加しようとしましたが、製品と要素の間のリンクが見つかりません...既存の親エンティティを、まだ追加されていない新しい子エンティティに更新するにはどうすればよいですか?

私は、Entity Framework 4.1とコードファーストを使用します。

私を助けることができますように!

答えて

36

まず、製品 エンティティの既存の成分収集をクリアし、更新された成分リストを再度追加します。

これは、子コレクションを更新するための種類のブルートフォース攻撃です。 EFには、親の状態をModifiedに設定するだけで、新しい子の追加、削除された子の削除、既存の子の更新といった子を更新する魔法はありません。基本的には、この手順はそうのように、データベースからも古い子供を削除し、新しいものを挿入するように強制します

// product is the detached product with the detached new children collection 
using (var context = new MyContext()) 
{ 
    var productInDb = context.Products.Include(p => p.Ingredients) 
     .Single(p => p.Id == product.Id); 

    // Update scalar/complex properties of parent 
    context.Entry(productInDb).CurrentValues.SetValues(product); 

    foreach (var ingredient in productInDb.Ingredients.ToList()) 
     context.Ingredients.Remove(ingredient); 

    productInDb.Ingredients.Clear(); // not necessary probably 

    foreach (var ingredient in product.Ingredients) 
     productInDb.Ingredients.Add(ingredient); 

    context.SaveChanges(); 
} 

よりよい手順は、データベース内のすべての子を削除せずにメモリ内の子供のコレクションを更新することです。

// product is the detached product with the detached new children collection 
using (var context = new MyContext()) 
{ 
    var productInDb = context.Products.Include(p => p.Ingredients) 
     .Single(p => p.Id == product.Id); 

    // Update scalar/complex properties of parent 
    context.Entry(productInDb).CurrentValues.SetValues(product); 

    var ingredientsInDb = productInDb.Ingredients.ToList(); 
    foreach (var ingredientInDb in ingredientsInDb) 
    { 
     // Is the ingredient still there? 
     var ingredient = product.Ingredients 
      .SingleOrDefault(i => i.Id == ingredientInDb.Id); 

     if (ingredient != null) 
      // Yes: Update scalar/complex properties of child 
      context.Entry(ingredientInDb).CurrentValues.SetValues(ingredient); 
     else 
      // No: Delete it 
      context.Ingredients.Remove(ingredientInDb); 
    } 

    foreach (var ingredient in product.Ingredients) 
    { 
     // Is the child NOT in DB? 
     if (!ingredientsInDb.Any(i => i.Id == ingredient.Id)) 
      // Yes: Add it as a new child 
      productInDb.Ingredients.Add(ingredient); 
    } 

    context.SaveChanges(); 
} 
+0

これは完全に完璧に動作します!ありがとう! – DirkDooms

+0

エンティティを正しく更新する方法を知りました。 context.Entry(productInDb).CurrentValues.SetValues(product);ありがとうございます。 –

+0

これは素晴らしく、今はリフレクションで動作させる方法を理解するだけで、保存しているエンティティのすべてのコレクションをループすることができます。 – Nikkoli

-2

私はこれが誰かを助けることができ、私が耐えてきた欲求不満を経験しないことを願っています。

public void SaveOrder(SaleOrder order) 
     { 
      using (var ctx = new CompanyContext()) 
      { 
       foreach (var orderDetail in order.SaleOrderDetails) 
       { 
        if(orderDetail.SaleOrderDetailId == default(int)) 
        { 
         orderDetail.SaleOrderId = order.SaleOrderId; 
         ctx.SaleOrderDetails.Add(orderDetail); 
        }else 
        { 
         ctx.Entry(orderDetail).State = EntityState.Modified; 
        } 
       } 

       ctx.Entry(order).State = order.SaleOrderId == default(int) ? EntityState.Added : EntityState.Modified; 
       ctx.SaveChanges();     

      } 

     } 
+0

削除された注文詳細は考慮していないようです –

4

私はDbContextのためGraphDiff延長線上に今回のarticleを見つけました。

明らかに、Slaumasolutionの一般的な再利用可能な変形です。

例コード:サイドノートで

using (var context = new TestDbContext()) 
{ 
    // Update DBcompany and the collection the company and state that the company 'owns' the collection Contacts. 
    context.UpdateGraph(company, map => map.OwnedCollection(p => p.Contacts));  
    context.SaveChanges(); 
} 

。私は、著者が問題#864 Provide better support for working with disconnected entitiesで彼のコードを使用するようにEFチームに提案していることがわかります。

-1

これはもっと簡単な解決策です。

public Individual 
{ 
..... 

public List<Address> Addresses{get;set;} 


} 

//where base.Update from Generic Repository 
public virtual void Update(T entity) 
     { 
      _dbset.Attach(entity); 
      _dataContext.Entry(entity).State = EntityState.Modified; 
     } 

//overridden update 
public override void Update(Individual entity) 
     { 


      var entry = this.DataContext.Entry(entity); 
      var key = Helper.GetPrimaryKey(entry); 
      var dbEntry = this.DataContext.Set<Individual>().Find(key); 

      if (entry.State == EntityState.Detached) 
      { 
       if (dbEntry != null) 
       { 
        var attachedEntry = this.DataContext.Entry(dbEntry); 
        attachedEntry.CurrentValues.SetValues(entity); 
       } 
       else 
       { 
        base.Update(entity); 
       } 
      } 
      else 
      { 
       base.Update(entity); 
      } 
      if (entity.Addresses.Count > 0) 
      { 
       foreach (var address in entity.Addresses) 
       { 
        if (address != null) 
        { 
         this.DataContext.Set<Address>().Attach(address); 
         DataContext.Entry(address).State = EntityState.Modified; 
        } 
       } 
      } 
     } 
関連する問題