实体框架代码首先是多对多共享元素 [英] Entity Framework Code First Many-To-Many with shared elements

查看:118
本文介绍了实体框架代码首先是多对多共享元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I need to create a many-to-many table relationship in which the elements of one table are shared by the elements of a second table. I've read the multitude of similar posts, but I'm clearly still wrong somewhere.

我将问题简化为这个简单的例子。我有两张桌子:饮料和配料。我想要在饮料中共享成分。

I've reduced my problem to this simple example. I have two tables: Beverages and Ingredients. I want the Ingredients to be shared among the Beverages.

我是如何创建桥表的问题? (我可以没有桥表吗?)或是我如何管理数据上下文的问题?

Is the problem how I've created the bridge table? (Can I do without a bridge table?) Or is the problem in how I'm managing the data context?

我有一个GetIngredient方法用于检索参考所需的成分。如果没有成分存在,就会创建一个新的成分。

I have a GetIngredient method with is intended to retrieve a reference to the desired Ingredient. If no Ingredient exists, a new ingredient is created.

所有这些都是如预期的,直到完成添加饮料的ctx.SaveChanges()。创建新的成分,使每个饮料都有独家的成分列表。这违背了我的意图。

All is as expected until the ctx.SaveChanges() which completes the add of the beverages. New ingredients are created such that each Beverage has an exclusive list of Ingredients. This is contrary to my intent.

请指教。

  [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

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 ,它将创建一个新的。

The problem is how you are managing the data context. In your code, you are using different instances of dbcontext, which might be cause of this problem. Every time you call GetIngredient, it is creating a new one.

此外,您应该每个方法只调用一次 SaveChanges 。如果在第一次保存更改后发生错误,则可能会出现数据不一致。

In addition, you should call SaveChanges only once per method. If an error occurs after the first save changes, you might have inconsistent data.

要解决此问题,您可以使用recentIngredients列表。像这个例子一样:

To solve this issue, you can use an "recentIngredients" list. Like in this example:

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列表在方法的最后。

Remember to always kill the recentIngredients list at the end of the method.

希望它有帮助!

这篇关于实体框架代码首先是多对多共享元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆