2012-03-01 9 views
0

これは私の状況です - 私はいくつかのテーブルレシピ成分recipes_ingredientsという名前のテーブルを持っているDBを持っています。EF経由で基礎となるルックアップをよりよく更新する方法はありますか?

レシピは1+成分で構成されています。

recipes_ingredientsレシピ成分テーブル間FKSを有しています。

生成されますクラスはrecipeingredientあるとrecipeはそうのように見えるナビゲーションプロパティがあります。

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

グレートを、私は、生成recipeクラスと生成されたingredientクラスを得ることを理解し、 recipes_ingredientsではありません。これは、EFが単純にナビゲーションプロパティーとして表示するため、クラスを生成します。名前が示すように、成分のリストを更新することで、

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(); 
    } 
} 

意図:

今、私は簡潔のために(マイナスとても似のtry-catchコードを探しますSetIngredientsForRecipeと呼ばれる機能を持っています唯一のリストにあるものは何でも。私はまだ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で何が行われたのか(別名リストを渡す)に基づいて、どのアクションを取るべきかを把握することはできませんか? –

+0

UIからのリスト( 'ingredients'パラメータ)は、DBが現在言っていることにかかわらず、成分となるべきものについての"真理 "しか持っていません。私はUIでこれを知ることができたかもしれませんが、単に「UI」と言っているだけの単純さのように、このレシピにあると思われるものを教えてください。しかし、はい、結果は同じになります。 – itsmatt

答えて

2

パフォーマンスに関する一般的なガイドラインは次のとおりです。

  • のidので唯一
  • モックエンティティ、可能な限りの対処しようとするのではなく、
  • デシベルからそれらを取得簡素化するために、ContainsようEF4の新機能を使用コードを高速化する

これらの原則に基づいて、最適化された(単純ではありませんが)解決策があります:

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

1

あなたはFirstOrDefaultの呼び出しを使用してWhere節凝縮することができます:私は個人的に、私は違いが正確に何であるかはわからないけれどもSingleOrDefaultを使用することを好むものの

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

を:

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

また、 (材料IDのリストとは対照的に)List<string>であるため、このプロセスの一部として新しい成分が生成される可能性があります。 (ただし、簡潔にするために省略されている可能性があります)。

+0

ショートカットありがとうございます。うーん、 'FirstOrDefault'は複数のものが存在することを許し、' SingleOrDefault'は複数ある場合にスローします。私の食材はユニークなものなので、「FirstOrDefault」が高速かもしれないのだろうと思っていますが、いずれも動作します。見るべきこと。 2番目の部分については、現在の成分は整数のIDではなく固有の文字列で識別されます。それは将来変わる可能性があると思いますが、現時点ではテーブルはIDカラムを使用して作成されています。 – itsmatt

+2

'First'と' Single'の違いは、述語と一致する要素が見つかるまで、 'First'がシーケンスを反復するということです。その時点で反復は停止します。 'Single'はシーケンス全体を繰り返し、2つ以上の要素が述語にマッチすれば例外をスローします。 'FirstOrDefault'と' SingleOrDefault'は、一致するものがなければデフォルト値が返される以外は同様の動作をします。これにより、 'FirstOrDefault'がより効率的になりますが、' SingleOrDefault'を検証に使用することができます。 –

+0

@MartinLiversage - 説明をありがとう。 –