我需要创建一个多对多表关系,其中一个表的元素由第二个表的元素共享。我已经阅读了大量相似的帖子,但我在某处显然仍然存在错误。
我已将问题简化为这个简单的例子。我有两张桌子:饮料和配料。我希望在饮料中分享配料。
我是如何创建桥接表的? (我可以不使用桥接表吗?)或者我是如何管理数据上下文的?
我有一个GetIngredient方法,用于检索对所需成分的引用。如果不存在成分,则会创建新成分。
所有这些都是预期的,直到完成添加饮料的ctx.SaveChanges()。创建新成分,使每个饮料都有一个独家的成分列表。这与我的意图相反。
请告知。
[Fact]
public void VerifyManyToMany()
{
using (var ctx = new CoffeeDbContext())
{
// Latte = espresso, steamed milk
// Macchiato = espresso, milk foam
Beverage beverage1 = new Beverage();
beverage1.Name = "Latte";
beverage1.Ingredients.Add( GetIngredient("espresso"));
beverage1.Ingredients.Add( GetIngredient( "steamed milk"));
ctx.Beverages.Add(beverage1);
Beverage beverage2 = new Beverage();
beverage2.Name = "Macchiato";
beverage2.Ingredients.Add(GetIngredient("espresso"));
beverage2.Ingredients.Add(GetIngredient("milk foam"));
ctx.Beverages.Add(beverage2);
// prior to this line, Ingredient table comprised of:
// {"espresso", "steamed milk", "milk foam"}
ctx.SaveChanges();
// after this line, Ingredient table comprised of:
// {"espresso", "steamed milk", "milk foam", "espresso", "espresso", "steamed milk", "milk foam"}
List<Ingredient> ingredientList = ctx.Ingredients.ToList();
Assert.True( ingredientList.Count == 2);
}
}
/// <summary>
/// Retrieve ingredient of designated name.
/// If no ingredient exists, create new ingredient.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
private Ingredient GetIngredient(string name)
{
using (var ctx = new CoffeeDbContext())
{
Ingredient ingredient = ctx.Ingredients.SingleOrDefault(i => i.Name == name);
if (ingredient == null)
{
ingredient = new Ingredient { Name = name };
ctx.Ingredients.Add(ingredient);
ctx.SaveChanges();
}
return ingredient;
}
}
饮料类
public class Beverage
{
public Beverage()
{
this.Ingredients = new HashSet<Ingredient>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual int BeverageId { get; set; }
public virtual string Name { get; set; }
// many-to-many relationship, every Beverage comprised meany ingredients
public virtual ICollection<Ingredient> Ingredients { get; private set; }
}
成分类
public class Ingredient
{
public Ingredient()
{
this.Beverages = new List<Beverage>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual int IngredientId { get; set; }
public virtual string Name { get; set; }
// many-to-many relationship, Ingredient used by many Beverages
public ICollection<Beverage> Beverages { get; private set; }
}
的DbContext
public class CoffeeDbContext : DbContext
{
public DbSet<Beverage> Beverages { get; set; }
public DbSet<Ingredient> Ingredients { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Beverage>()
.HasMany(x => x.Ingredients)
.WithMany(x => x.Beverages)
.Map(
m =>
{
m.MapLeftKey("BeverageId");
m.MapRightKey("IngredientId");
m.ToTable("BeverageIngredients");
}
);
}
}
答案 0 :(得分:0)
问题是您如何管理数据上下文。在您的代码中,您使用的是dbcontext的不同实例,这可能是导致此问题的原因。每次拨打GetIngredient
时,都会创建一个新的。
此外,每种方法只应调用SaveChanges
一次。如果在第一次保存更改后发生错误,则可能存在不一致的数据。
要解决此问题,您可以使用“recentIngredients”列表。就像在这个例子中一样:
class Program
{
static void Main(string[] args)
{
using (var ctx = new CoffeeDbContext())
{
// Latte = espresso, steamed milk
// Macchiato = espresso, milk foam
Beverage beverage1 = new Beverage();
beverage1.Name = "Latte";
beverage1.Ingredients.Add(GetIngredient(ctx, "espresso"));
beverage1.Ingredients.Add(GetIngredient(ctx, "steamed milk"));
ctx.Beverages.Add(beverage1);
Beverage beverage2 = new Beverage();
beverage2.Name = "Macchiato";
beverage2.Ingredients.Add(GetIngredient(ctx, "espresso"));
beverage2.Ingredients.Add(GetIngredient(ctx, "milk foam"));
ctx.Beverages.Add(beverage2);
// prior to this line, Ingredient table comprised of:
// {"espresso", "steamed milk", "milk foam"}
// call save changes only once, it will add all new ingredients automatically
ctx.SaveChanges();
// after this line, Ingredient table comprised of:
// {"espresso", "steamed milk", "milk foam", "espresso", "espresso", "steamed milk", "milk foam"}
//see the result here!
List<Ingredient> ingredientList = ctx.Ingredients.ToList();
Console.ReadKey();
}
}
private static List<Ingredient> recentIngredients = new List<Ingredient>();
//do not create another instance of dbcontext here, use a parameter
private static Ingredient GetIngredient(CoffeeDbContext ctx, string name)
{
//first, check if it was recently added
//if it was, just bring it from the temp collection
var recentIngredient = recentIngredients.SingleOrDefault(i => i.Name == name);
if (recentIngredient != null)
return recentIngredient;
//if it was not, check in database
Ingredient ingredient = ctx.Ingredients.SingleOrDefault(i => i.Name == name);
//if it exists in database, just return
if (ingredient == null)
{
//if it does not, create a new ingredient and add it to the temp list
ingredient = new Ingredient { Name = name };
recentIngredients.Add(ingredient);
}
return ingredient;
}
}
请记住始终在方法结尾处杀死recentIngredients列表。
希望它有所帮助!