Entity Framework Code First 多对多共享元素
Entity Framework Code First Many-To-Many with shared elements
我需要创建一个多对多 table 关系,其中一个 table 的元素由第二个 table 的元素共享。我已经阅读了大量类似的帖子,但我显然仍然在某处错误。
我已将我的问题简化为这个简单的示例。我有两个 table:饮料和配料。我希望在饮料之间共享成分。
问题是我如何创建桥 table 吗? (我可以不用网桥吗?table?)还是我管理数据上下文的方式有问题?
我有一个 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;
}
}
饮料Class
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; }
}
成分Class
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");
}
);
}
}
问题在于您如何管理数据上下文。在您的代码中,您使用了不同的 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 列表。
希望对您有所帮助!
我需要创建一个多对多 table 关系,其中一个 table 的元素由第二个 table 的元素共享。我已经阅读了大量类似的帖子,但我显然仍然在某处错误。
我已将我的问题简化为这个简单的示例。我有两个 table:饮料和配料。我希望在饮料之间共享成分。
问题是我如何创建桥 table 吗? (我可以不用网桥吗?table?)还是我管理数据上下文的方式有问题?
我有一个 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;
}
}
饮料Class
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; }
}
成分Class
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");
}
);
}
}
问题在于您如何管理数据上下文。在您的代码中,您使用了不同的 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 列表。
希望对您有所帮助!