Home > Software engineering >  How to save collection of foreign keys in code first
How to save collection of foreign keys in code first

Time:04-14

Update: At this moment I can assign only one product to Recipe. What I want to do is add access to all products from db in recipe (controller create) - here Im using public int ProductId but it allow to save only one product. I want to choose a few products from this list and save foreign keys in database. photo

I also tried add public List < int > in ProductId but I got error from entity framework.

I will be grateful for any help.

public class Recipe
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        [Required]
        public int ProductId { get; set; }

        public List<Product> Products { get; set; }

        public Recipe()
        {
            this.Products = new List<Product>();
        }
    }

    public class Product
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        [Required]
        public Recipe? Recipes { get; set; }
    }

CodePudding user response:

If you want to create a one-to-many relationship you are almost in the correct direction, but you should remove the public int ProductId { get; set; } and re-arrange as like as below example.

Say you have the following classes:

    public class Recipe
    {
        [Key]
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }

        public List<Product> Products { get; set; } = new();
    }

    public class Product
    {
        [Key]
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }

        [Required]
        public Recipe Recipe { get; set; }
    }

You can instantiate and use as per below:

    public static void Main()
    {
        var recipe = new Recipe
        {
            Name = "My Recipe",
            Products = new List<Product>
            {
                new Product { Name = "Product 1" },
                new Product { Name = "Product 2" },
                new Product { Name = "Product 3" }
            }
        };

        recipe.Products.ForEach(product =>
        {
            Console.WriteLine(product.Name);
        });
    }

CodePudding user response:

It sounds like you are looking for a many-to-many relationship rather than a one-to-many.

If you are using Code-First and EF6 or EF Core 5 then you can use:

public class Recipe
{
    [Key]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }

    public virtual ICollection<Product> Products { get; set; } = new List<Product>();

}

public class Product
{
    [Key]
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }

    public virtual ICollection<Recipe> Recipes { get; set; } = new List<Recipe>();
}

To understand what is happening behind the scenes, EF should create a joining table called ProductRecipe or RecipeProduct which contains two FKs. ProductId and RecipeId. These will also form a composite PK for this table. Using this table, EF can associate one product to several recipes while also associating one recipe to the various products. In the object model you get the collection of products for each recipe, and the collection of recipes for each product.

With earlier versions of EF Core you were limited to having to declare this linking entity so you would have something more like:

public class Recipe
{
    // ...

    public virtual ICollection<ProductRecipe> ProductRecipes { get; set; } = new List<ProductRecipe>();

}

public class Product
{
    // ...
    public virtual ICollection<ProductRecipe> ProductRecipes { get; set; } = new List<ProductRecipe>();
}

public class ProductRecipe
{
    [Key, Column(Order = 0)]
    public int ProductId { get; set; }
    [Key, Column(Order = 1)]
    public int RecipeId { get; set; }

    public virtual Product Product { get; set; }
    public virtual Recipe Recipe { get; set; }
}

This approach is still an option in the other versions of EF, and is required if you want to support adding any additional fields to the joining table. For instance if you want to track things like CreatedDate/ModifiedDate etc. to record when a product was associated to a recipe etc. To expose that information to an application through EF, EF needs to know about the ProductRecipe as an entity. The trade off is that this approach is "messier" when usually you care about the Products for a Recipe etc. To get a list of products for a recipe you would need:

var products = context.Products
    .Where(p => p.ProductRecipes.Any(pr => pr.RecipeId == recipeId)
    .ToList();

or

var products = context.Recipies
    .Where(r => r.RecipeId == recipeId)
    .SelectMany(r => r.ProductRecipies.Select(pr => pr.Product).ToList())
    .ToList();

vs. the implied joining table in the first approach:

var produts = context.Recipes
    .Where(r => r.RecipeId == recipeId)
    .SelectMany(r => r.Products)
    .ToList();

... which is arguably easier to read.

  • Related