2012-03-01 42 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