实体框架代码首先使用共享元素的多对多

时间:2015-12-30 15:20:22

标签: entity-framework many-to-many code-first

我需要创建一个多对多表关系,其中一个表的元素由第二个表的元素共享。我已经阅读了大量相似的帖子,但我在某处显然仍然存在错误。

我已将问题简化为这个简单的例子。我有两张桌子:饮料和配料。我希望在饮料中分享配料。

我是如何创建桥接表的? (我可以不使用桥接表吗?)或者我是如何管理数据上下文的?

我有一个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");
            }
         );
   }
}

1 个答案:

答案 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列表。

希望它有所帮助!

相关问题