2012-03-01 65 views
0

這裏是我的情況 - 我有這有一個名爲食譜成分recipes_ingredients一些表的DB。通過EF更新底層查找的更好方法?

食譜是由1+成分組成。

recipes_ingredients具有食譜成分表之間FKS。

是那些獲得生成的類是recipeingredientrecipe有一個看起來像這樣的導航屬性:

public virtual ICollection<ingredients> ingredients { get; set; }

太好了,我明白,我得到一個產生recipe類和生成的ingredientrecipes_ingredients不會獲得一個類,因爲EF將其視爲一個導航屬性。現在

,我有一個叫做SetIngredientsForRecipe功能,看起來像這樣(減去在try-catch代碼爲簡潔起見:

public void SetIngredientsForRecipe(long recipeId, List<string> ingredients) 
{ 
    using (var db = new FoodEntities(ConnectionString, null, null)) 
    { 
     var existing = GetCurrentIngredients(recipeId); 
     var toRemove = existing.Except(ingredients); 
     var toAdd = ingredients.Except(existing); 
     var recipe = db.recipes.Where(r => r.Id == recipeId).FirstOrDefault(); 
     foreach (var name in toRemove) 
     { 
     var entry = recipe.ingredients.Where(i => i.Name == name).FirstOrDefault(); 
     recipe.ingredients.Remove(entry); 
     } 
     foreach (var name in toAdd) 
     { 
     var entry = db.ingredients.Where(i => i.Name == name).FirstOrDefault(); 
     recipe.ingredients.Add(entry); 
     } 
     db.SaveChanges(); 
    } 
} 

這樣做的目的,顧名思義,是更新成分表對於給定的食譜只能無論是在列表中。我仍然獲得舒適與EF和想知道是否有一個更好的(更有效?)的方式來完成我想要做的事。


後續:

ntziolis下面的建議,我選擇使用

recipe.ingredients.Clear()以清除任何在配方/成分映射,然後用它提到了對快速添加新的嘲諷。類似這樣的:

foreach (var name in ingredients) 
{ 
    // Mock an ingredient since we just need the FK that is referenced 
    // by the mapping table - the other properties don't matter since we're 
    // just doing the mapping not inserting anything 

    recipe.ingredients.Add(new Ingredient() 
    { 
    Name = name 
    }); 
} 

並且這個工作非常好。

+0

因此,成分是否會出現在列表中,無論是添加還是去除?你不應該能夠根據在UI中完成的操作來確定要採取哪種操作(並傳入不同的列表)嗎? – 2012-03-01 13:42:10

+0

無論數據庫當前如何表示,來自UI(「配料」參數)的列表僅具有關於應該是什麼成分的「真相」。我*可能*可能在用戶界面中知道這一點,但只需簡單地說一下「用戶界面,告訴我這個配方中應該包含什麼」。但是,結果是一樣的。 – itsmatt 2012-03-01 13:55:35

回答

2

一般性能準則是:

  • 嘗試處理ID唯一
  • 模擬實體只要有可能,而不是從數據庫
  • 使用EF4的新功能,如Contains爲了簡化檢索它們並加快您的代碼

基於這些原則,這裏是一個優化(雖然不簡單)解決您的問題:

public void SetIngredientsForRecipe(long recipeId, List<string> ingredients) 
{ 
    using (var db = new FoodEntities(ConnectionString, null, null)) 
    { 
     var recipe = db.recipe.Single(r => r.ID == recipeId); 

     // make an array since EF4 supports the contains keyword for arrays 
     var ingrArr = ingredients.ToArray(); 

     // get the ids (and only the ids) of the new ingredients 
     var ingrNew = new HasSet<int>(db.ingrediants 
     .Where(i => ingrArr.Contains(i.Name)) 
     .Select(i => I.Id)); 

     // get the ids (again only the ids) of the current receipe 
     var curIngr = new HasSet<int>(db.receipes 
     .Where(r => r.Id == recipeId) 
     .SelectMany(r => r.ingredients) 
     .Select(i => I.Id));   

     // use the build in hash set functions to get the ingredients to add/remove    
     var toAdd = ingrNew.ExpectWith(curIngr); 
     var toRemove = curIngr.ExpectWith(ingrNew); 

     foreach (var id in toAdd) 
     { 
     // mock the ingredients rather than fetching them, for relations only the id needs to be there 
     recipe.ingredients.Add(new Ingredient() 
     { 
      Id = id 
     }); 
     } 

     foreach (var id in toRemove) 
     { 
     // again mock only 
     recipe.ingredients.Remove(new Ingredient() 
     { 
      Id = id 
     }); 
     } 

     db.SaveChanges(); 
    } 
} 

如果你想更簡單,你可以只清除所有成分,並重新在必要時添加它們,EF甚至可能是足夠聰明弄清楚,關係沒有發生變化,不知道這件事,但:

public void SetIngredientsForRecipe(long recipeId, List<string> ingredients) 
{ 
    using (var db = new FoodEntities(ConnectionString, null, null)) 
    {  
    var recipe = db.recipe.Single(r => r.ID == recipeId); 

    // clear all ingredients first 
    recipe.ingredients.Clear() 

    var ingrArr = ingredients.ToArray(); 
    var ingrIds = new HasSet<int>(db.ingrediants 
     .Where(i => ingrArr.Contains(i.Name)) 
     .Select(i => I.Id)); 

    foreach (var id in ingrIds) 
    { 
     // mock the ingredients rather than fetching them, for relations only the id needs to be there 
     recipe.ingredients.Add(new Ingredient() 
     { 
     Id = id 
     }); 
    } 

    db.SaveChanges(); 
    } 
} 

UPDATE
一些編碼錯誤已經被修正。

+0

嗨,感謝您的想法。我正在考慮模擬的想法。所以這裏的想法是,我只需要成分對象的主鍵部分,以便底層表可以更新,對吧?所以,雖然我的'Ingredient'類有很多其他屬性,我不關心任何導航屬性,只是標識符。有趣。再次感謝。 – itsmatt 2012-03-01 14:28:01

1

您可以用FirstOrDefault電話凝結在Where條款:

recipe.ingredients.FirstOrDefault(i => i.Name == name); 

雖然我個人更喜歡使用SingleOrDefault,雖然我不知道的區別到底是什麼:

recipe.ingredients.SingleOrDefault(i => i.Name == name); 

而且,因爲傳入的成分列表是List<string>(與成分ID列表相對),這意味着也可以創建新成分作爲此過程的一部分,而不是處理(雖然可能爲了簡潔而被忽略)。

+0

感謝您的快捷方式。是的,'FirstOrDefault'允許存在倍數,如果有多個'SingleOrDefault'會拋出。由於我的原料是獨一無二的,我猜想或者可行,但我想知道'FirstOrDefault'可能會更快。有東西要看。至於第二部分,目前的成分是由唯一的字符串而不是整數ID來標識的。我想,這可能會在未來發生變化,但目前這張桌子已經建成了一個Id列。 – itsmatt 2012-03-01 14:09:47

+2

「First」和「Single」之間的差異在於「First」將迭代序列,直到找到與謂詞匹配的元素。此時迭代將停止。如果多個元素匹配謂詞,Single將迭代整個序列並拋出異常。 'FirstOrDefault'和'SingleOrDefault'具有類似的行爲,只是如果不匹配則返回默認值。這使得'FirstOrDefault'更高效,但'SingleOrDefault'可以用於驗證。 – 2012-03-01 14:10:42

+0

@MartinLiversage - 感謝您的澄清。 – 2012-03-01 14:12:17