2014-05-12 96 views
1

我使用EF4的經驗非常有限。 我成功反序列化處於分離狀態的web服務的實體,現在我想保存到數據庫。 當使用的SaveChanges我得到以下異常:導致重複ID問題的獨立實體

System.Data.UpdateException:更新 條目中出現了錯誤。詳情請參閱內部例外。 ---> System.Data.SqlClient.SqlException:違反主鍵約束'[主鍵約束名]' '。不能在對象'[相關表名]'中插入重複的 鍵。重複的鍵值是(1)。 該聲明已被終止。

我試圖保存的實體將相關實體作爲實體集合的屬性和屬性。

來自Web服務的ID用作表的主鍵,因此不使用自動生成的ID。

下面的測試說明,我試圖解決這個問題:

[TestMethod] 
    public void SaveRelatedDetachedEntitiesWithoutDuplicatesTest(){ 
     using (var db = ProductEntities()){ 
      //testing pre-saved existing category 
      if (!db.CategoryGroups.Any(e => e.Id == 3)){ 
       db.CategoryGroups.AddObject(new Database.CategoryGroupEntity(){ 
                       Id = 3, 
                       Name = "test group 3" 
                      }); 
       db.SaveChanges(); 
      } 

      var categoryList = new List<CategoryEntity>(){ 
       new CategoryEntity(){ 
        Id = 1, 
        Name = "test category 1", 
        Groups = new List<CategoryGroupEntity>(){new CategoryGroupEntity(){ 
                        Id = 1, 
                        Name = "test group 1" 
                       },//duplicate 
                       new CategoryGroupEntity(){ 
                        Id = 2, 
                        Name = "test group 2" 
                       } 
                      } 
       },  
       new CategoryEntity(){ 
        Id = 2, 
        Name = "test category 2", 
        Groups = new List<CategoryGroupEntity>{ 
                      new CategoryGroupEntity(){ 
                       Id = 1, 
                       Name = "test group 1" 
                      },//duplicate 
                      new CategoryGroupEntity(){ 
                       Id = 3, 
                       Name = "test group 3" 
                      }//already in db 
                     } 
       } 
      }; 

      var product = new ProductEntity(){   
       Categories = categoryList,   
       Id = 1, 
       Name = "test product 1",    
       Type = new TypeEntity { Id = 1, Name = "test type" } 
      }; 
//all above code cannot be altered as it reflects what results from the deserialization. 
      db.Products.AddObject(product); 

//need code here to handle the duplicates 
      db.SaveChanges(); 

      var test = db.Products.Where(e => e.Id == 1).FirstOrDefault(); 
      Assert.IsNotNull(test); 
      Assert.IsTrue(test.Categories.Count() == 2, "missing categories from product"); 
      Assert.IsTrue(test.Categories.ElementAt(0).Groups.Any(e => e.Id == 1), "missing group from category 1"); 
      Assert.IsTrue(test.Categories.ElementAt(1).Groups.Any(e => e.Id == 1), "missing group from category 2"); 
     } 
    } 

您的幫助表示讚賞。

編輯: 我可以得到的是使用下面的代碼

   var added = db.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added) 
        .Where(e => !e.IsRelationship).Select(e => e.Entity) 
        .OfType<CategoryGroupEntity>(); 
       var duplicates = added.GroupBy(e => e.Id) 
        .Where(g => g.Count() > 1) 
        .SelectMany(g => g.Where(e => e != g.First()) 

事情我已經嘗試了重複,但沒有工作組的列表:

-attaching實體是重複的,以數據上下文的狀態不變。 作爲附加CategoryGroupEntity導致附加的所有相關實體重複的關鍵問題仍然

從分類收集的拆卸,實體實例,並與第一次創造了同樣的問題

結果CategoryGroupEntity實例替換他們 - 分離重複的實體實例會導致第二個類別丟失組ID 1

作爲一個方面說明,當數據庫中已存在特定的CategoryGroupEntity並嘗試保存實體時,我還需要避免重複密鑰問題使用相同的ID。

所以,我需要避免重複的關鍵問題,當具有該ID的實體存在於數據庫中或在ObjectStateManager中添加狀態時。我上面包含的測試合併了這兩種情況。

+0

爲什麼實體框架重新插入現有的對象到我的數據庫? msdn.microsoft.com/en-us/magazine/dn166926.aspx – Colin

+0

[如何避免重複插入在實體框架4.3.1]可能的重複(http://stackoverflow.com/questions/13285485/how-to-avoid -duplicate-insert-in-entity-framework-4-3-1) – Colin

回答

0

我得到它的工作,但它絕對不是這樣做的最佳方式。

保存方法我用已被列入下面:

public static void SaveProduct(ProductEntity product) { 
     using (var db = ProductEntities()) { 


      //stored references to duplicate entities added to the objectContext 
      var duplicateGroupsAdded = new List<Tuple<CategoryEntity, GroupEntity>>(); 
      var duplicateCategoriesAdded = new List<Tuple<ProductEntity, CategoryEntity>>(); 

      //using existing instace of entities includes associated parent into db update causing duplicate product insert attempt. 
      //entities saved must be newly instantiated with no existing relationships. 
      var categories = product.Categories.ToList(); 
      var type = new TypeEntity() { 
       Id = product.Type.Id, 
       Name = product.Type.Name 
      }; 

      //empty the collection 
      product.Categories.ToList().ForEach(category => { 
       product.Categories.Remove(category); 
      }); 
      //start off with clean product that we can populate with related entities 
      product.Type = null; 
      product.Group = null; 

      //add to db 

      db.Products.AddObject(product); 

      categories.ForEach(oldCategory => { 
       //new cloned category free of relationships 
       var category = new CategoryEntity() { 
        Id = oldCategory.Id, 
        Name = oldCategory.Name 

       }; 
       //copy accross Groups as clean entities free of relationships 
       foreach (var group in oldCategory.Groups) { 
        category.Groups.Add(new GroupEntity() { 
         Id = group.Id, 
         Name = group.Name 
        }); 
       } 
       //if the cat is alreay in the db use reference to tracked entity pulled from db 
       var preexistingCategory = db.Categories.SingleOrDefault(e => e.Id == category.Id); 
       if (preexistingCategory != null) 
        product.Categories.Add(preexistingCategory); 
       else { 
        //category not in database, create new 
        var Groups = category.Groups.ToList(); 
        category.Groups.ToList().ForEach(group => category.Groups.Remove(group)); 
        Groups.ForEach(Group => { 
         //if the group is alreay in the db use reference to tracked entity pulled from db 
         var preexistingGroup = db.Groups.SingleOrDefault(e => e.Id == Group.Id); 
         if (preexistingGroup != null) 
          category.Groups.Add(preexistingGroup); 
         else 
          category.Groups.Add(Group); 
        }); 
        product.Categories.Add(category); 
       } 
      }); 
      //if the type is alreay in the db use reference to tracked entity pulled from db 
      var preexistingType = db.Types.SingleOrDefault(e => e.Id == type.Id); 
      if (preexistingType != null) 
       product.Type = preexistingType; 
      else 
       product.Type = type; 

      //get lists of entities that are to be added to the database, and have been included in the update more than once (causes duplicate key error when attempting to insert). 
      var EntitiesToBeInserted = db.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added) 
           .Where(e => !e.IsRelationship).Select(e => e.Entity).ToList(); 
      var duplicateGroupInsertions = EntitiesToBeInserted 
            .OfType<GroupEntity>() 
            .GroupBy(e => e.Id) 
            .Where(g => g.Count() > 1) 
            .SelectMany(g => g.Where(e => e != g.First())); 

      var duplicateCategoryInsertions = EntitiesToBeInserted 
           .OfType<CategoryEntity>() 
           .GroupBy(e => e.Id) 
           .Where(g => g.Count() > 1) 
           .SelectMany(g => g.Where(e => e != g.First())); 

      foreach (var category in product.Categories) { 
       //remove duplicate insertions and store references to add back in later 
       var joinedGroups = duplicateGroupInsertions.Join(category.Groups, duplicateGroupInsertion => duplicateGroupInsertion, linkedGroup => linkedGroup, (duplicateGroupInsertion, linkedGroup) => duplicateGroupInsertion); 
       foreach (var duplicateGroupInsertion in joinedGroups) { 
        if (category.Groups.Contains(duplicateGroupInsertion)) { 
         category.Groups.Remove(duplicateGroupInsertion); 
         db.Groups.Detach(duplicateGroupInsertion); 
         duplicateGroupsAdded.Add(new Tuple<CategoryEntity, GroupEntity>(category, duplicateGroupInsertion)); 
        } 
       } 
      } 
      //remove duplicate insertions and store references to add back in later 
      var joinedCategories = duplicateCategoryInsertions.Join(product.Categories, duplicateCategoryInsertion => duplicateCategoryInsertion, linkedCategory => linkedCategory, (duplicateCategoryInsertion, linkedCategory) => duplicateCategoryInsertion); 
      foreach (var duplicateCategoryInsertion in joinedCategories) { 
       if (product.Categories.Contains(duplicateCategoryInsertion)) { 
        product.Categories.Remove(duplicateCategoryInsertion); 
        db.Categories.Detach(duplicateCategoryInsertion); 
        duplicateCategoriesAdded.Add(new Tuple<ProductEntity, CategoryEntity>(product, duplicateCategoryInsertion)); 
       } 
      } 
      db.SaveChanges(); 

      //entities not linked to product can now be added using references to the entities stored earlier 
      foreach (var duplicateGroup in duplicateGroupsAdded) { 
       var existingCategory = db.Categories.SingleOrDefault(e => e.Id == duplicateGroup.Item1.Id); 
       var existingGroup = db.Groups.SingleOrDefault(e => e.Id == duplicateGroup.Item2.Id); 
       existingCategory.Groups.Add(existingGroup); 
      } 
      foreach (var duplicateCategory in duplicateCategoriesAdded) { 
       product = db.Products.SingleOrDefault(e => e.Id == duplicateCategory.Item1.Id); 
       var existingCategory = db.Categories.SingleOrDefault(e => e.Id == duplicateCategory.Item2.Id); 
       product.Categories.Add(existingCategory); 
      } 

      db.SaveChanges(); 

     } 
    } 

任何進一步的建議,歡迎

+0

進一步的建議將會在[這篇文章](http://www.codeproject.com/Articles/576330/Attaching-detached-POCO-to-EF-DbContext-simple-and) – Sjoerd222888

+0

感謝您的建議@ Sjoerd2228888,不知道它是否完全符合法案因爲它依靠檢查空id來識別新條目,所以在我的情況條目中的條目在反序列化時提供了id,並且可能不存在於db中。 這意味着必須從數據庫加載連接的實體以檢查數據庫中是否已存在數據。 這會導致進一步的問題,因爲從數據庫加載的實體以及正在保存的實體以及其他可能的重複項也被反序列化。例如CategoryGroupEntity被反序列化了多次,並且可能已經存在於數據庫中。 –

0

在實體框架,如果你想要一個實體加入已經存在,則只是給它的主鍵無法正常工作,您需要加載該實體,併爲其分配

var product = new ProductEntity(){   
       Categories = categoryList,   
       Id = 1, 
       Name = "test product 1",    
       Type = new TypeEntity { Id = 1, Name = "test type" } 
     }; 

因此,在你的代碼示例TypeEntity與ID = 1時已經存在,那麼你需要上面的代碼更改爲類似

var product = new ProductEntity(){   
       Categories = categoryList,   
       Id = 1, 
       Name = "test product 1",    
       Type = db.TypeEntity.Find(1); 
     }; 

EF作用於對象,因此爲了說,你是不是修改的對象,但只是用它作爲關係船你需要找到那個對象和將其分配到要使用的位置。

更新類似的例如

public class Princess 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public virtual ICollection<Unicorn> Unicorns { get; set; } 
} 

var princess = context.Princesses.Find(#id);

// Load the unicorns related to a given princess using a string to 
// specify the relationship 
context.Entry(princess).Collection("Unicorns").Load(); 

來源: http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

+0

嗨,感謝您的及時迴應。 正如我所說的,這些實體在分離狀態下被反序列化,並且所有的屬性/集合都被預填充。 如何檢查實體是否存在,如果存在則使用它,如果不存在則保存。 這裏的主要關注類別的Groups集合。 我可以通過id加載Type並更新產品,但類別組要困難得多。 有什麼建議嗎? –

+0

我真的可以使用一些示例代碼來處理組織。我將更新測試代碼以反映我需要的內容。 –

+0

我正在嘗試在Stack Overflow聊天中找到你,但無法找到你。雖然你沒有分享你的模型,但我假設你將在你的模型中提到兩種關係。可能是具有ProductEntity的分類模型。如果你可以使用類似 db.Entry(產品) .Collection(c => c.Categories) .Query() 。Where(#some condition) .Load(); –