Лучший способ обновить базовый поиск через EF? - PullRequest
0 голосов
/ 01 марта 2012

Вот моя ситуация - у меня есть БД, в которой есть несколько таблиц с именами recipes , ингредиенты и recipes_ingredients .

Рецепты состоят из 1+ ингредиентов.

recipes_ingredients содержит FK между рецептами и ингредиентов таблицы.

Сгенерированные классы recipe и ingredient и recipe имеют свойство навигации, которое выглядит так:

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

Отлично, я понимаю, что я получил сгенерированный класс recipe и сгенерированный класс ingredient, и что таблица recipes_ingredients 1030 * не генерирует класс, поскольку 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 и задаюсь вопросом, есть ли лучший (более эффективный?) Способ выполнить то, что я пытаюсь сделать.


** * 1040 1041 * Последующий:

Следуя предложениям 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 ]

2 голосов
/ 01 марта 2012

Общие рекомендации по производительности:

  • попробуй разобраться только с идентификаторами
  • имитировать сущности всякий раз, когда это возможно, вместо извлечения их из БД
  • используйте новые функции 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
Некоторые ошибки кодирования были исправлены.

1 голос
/ 01 марта 2012

Вы можете сжать свои пункты Where с помощью вызовов FirstOrDefault:

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

Хотя я лично предпочитаю использовать SingleOrDefault, хотя я не уверен, в чем именно разница:

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

Кроме того, поскольку переданный список ингредиентов представляет собой List<string> (в отличие от списка идентификаторов ингредиентов), это как бы подразумевает, что новые ингредиенты также могут быть созданы как часть этого процесса, что не является обработано (хотя, возможно, для краткости было опущено).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...