通过EF更新基础查找的更好方法?

时间:2012-03-01 13:30:47

标签: c# entity-framework entity-framework-4.1

这是我的情况 - 我有一个数据库,其中有一些名为食谱成分 recipes_ingredients 的表。

食谱由1+种成分组成。

recipes_ingredients 食谱成分表之间有FK。

生成的类是recipeingredientrecipe有一个导航属性,如下所示:

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

很好,我知道我得到了一个生成的recipe类和一个生成的ingredient类,并且recipes_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
  });
}

这非常好用。

2 个答案:

答案 0 :(得分:2)

一般性能指南是:

  • 尝试仅处理id
  • 尽可能使用模拟实体,而不是从db
  • 中检索它们
  • 使用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();
  }
}

<强>更新
一些编码错误已得到纠正。

答案 1 :(得分:1)

您可以使用Where来电缩小FirstOrDefault条款:

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

虽然我个人更喜欢使用SingleOrDefault,但我不确定区别是什么:

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

此外,由于传入的成分列表是List<string>(而不是成分ID列表),因此它意味着也可以创建新成分作为此过程的一部分,这不是没有处理(虽然可能因为简洁而被遗漏)。