这是我的情况 - 我有一个数据库,其中有一些名为食谱,成分和 recipes_ingredients 的表。
食谱由1+种成分组成。
recipes_ingredients 在食谱和成分表之间有FK。
生成的类是recipe
和ingredient
,recipe
有一个导航属性,如下所示:
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
});
}
这非常好用。
答案 0 :(得分:2)
一般性能指南是:
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列表),因此它意味着也可以创建新成分作为此过程的一部分,这不是没有处理(虽然可能因为简洁而被遗漏)。