Search code examples
sqlasp.netrazormany-to-manyrelation

Adding to M:N tables ASP .NET Razor pages


I decided to try and learn ASP .NET Razor pages, so I came up with a personal project. I want to build a Recipes book to store all my recipes.

Basically I made many-to-many relation between 2 tables called Recipes and Ingredients (1 recipe can have many ingredients and 1 ingredient can be included in many recipes).

The idea is to add new recipe and on adding page to have multiple rows of dropdown menu where user can choose from the ingredient and input box to input quantity of the ingredient from the dropdown menu.

The relation works for displaying data but I also need to add recipes with the ingredients and I have no idea how to do it. Do I add just the IDs of the recipe and ingredients to the join table (the join table class does not exist in my code) or how can I do it?

I also want to add quantity of the ingredient. Do I add the quantity column to the join table RecipeIngredient?

I created 2 classes called RecipeEntity and IngredientEntity

public class RecipeEntity
    {
        public int Id { get; set; } 
        public string NameOfRecipe { get; set; }
        public string Author { get; set; } = "Uknown";
        public string Directions { get; set; }
        public ICollection<IngredientEntitty> Ingredients { get; set; }

    }
public class IngredientEntitty
    {
        public int Id { get; set; }
        public string NameOfIngredient { get; set; }
        public ICollection<RecipeEntity> Recipes { get; set; }
    }

Then created the DbContextClass

public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) 
            : base(options)
        {}

        public DbSet<RecipeEntity> Recipes { get; set; }
        public DbSet<IngredientEntitty> Ingredients { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<RecipeEntity>()
                .HasMany(x => x.Ingredients)
                .WithMany(y => y.Recipes)
                .UsingEntity(j => j.ToTable("RecipeIngredient"));
        }
    }

Now in my SQL database I have 3 tables: Ingredients, Recipes and join table RecipeIngredient.

Here is the .cshtml.cs file:

[BindProperties(SupportsGet = true)]
    public class AddNewRecipeModel : PageModel
    {
        private readonly BookOfRecipes.Data.ApplicationDbContext _context;

        public  AddNewRecipeModel(BookOfRecipes.Data.ApplicationDbContext context)
        {
            _context = context;
        }
        [BindProperty]
        public RecipeEntity Recipe { get; set; }

        public List<IngredientEntitty> ListOfIngredients { get; set; }

        public void OnGet()
        {
            ListOfIngredients = _context.Ingredients.ToList();
        }

        public async Task <IActionResult> OnPostAsync() 
        {
            _context.Recipes.Add(Recipe);
            await _context.SaveChangesAsync();
            return Page();
        }
    }

And .cshtml:

@page
@model BookOfRecipes.Pages.NewRecipe.AddNewRecipeModel
@{
}

<form method="post">
        <input type="text" name="Recipe.NameOfRecipe" asp-for="Recipe.NameOfRecipe" />
        <label for="recipeName">Name of the recipe</label>
        <input type="text" name="Recipe.Author" asp-for="Recipe.Author" />
        <label for="authorName">Author</label>
        <input type="text" name="Recipe.Directions" asp-for="Recipe.Directions" />
        <label for="directions">Directions</label>
        <h4>Ingredience</h4>
        <br>
        <select>
            @foreach (var ingredient in Model.ListOfIngredients)
            {
                <option>@ingredient.NameOfIngredient</option>
            }
        </select>
        <input type="text"/>
        <input type="submit">
</form>

In the second to last input I want to use to add quantity of the ingredient.


Solution

  • As described in the EF Core docs for many-to-many relationships with payload (ingredient count) the easiest options would be to just create a explicit join entity and DbSet. So something like the following:

    public class Recipe
    {
        public int Id { get; set; } 
        // ...
        public ICollection<RecipeIngredient> RecipeIngredients { get; set; }
    }
    
    public class RecipeIngredient
    {
        public int Id { get; set; }
        public int RecipeId { get; set; }
        public int IngredientId { get; set; }
        public int Amount { get; set; } // the payload
    
        public Recipe Recipe { get; set; }
        public Ingredient Ingredient { get; set; }
    }
    
    
    public class Ingredient
    {
        public int Id { get; set; }
        // ...
        public ICollection<RecipeIngredient> RecipeIngredients { get; set; }
    }
    

    As for the pages itself I highly recommend to introduce special types (the DTO's or ViewModels) as the types managed by page and use them and not the entities and map from entities to the classes (it will make easier to simulate the needed data structure to show and post and can make more convenient prevention of things like overposting attacks).