
Master and Salve Entities CRUD Using .Net Core MVC (Part 2)
- By: Waleed Ur Rehman
- April 26, 2022
How to create multi table (Master Slave) CRUD in .net core MVC.
Before starting this part please read Part 1 if you did not have read that one.
Let's start here is problem statement.
Problem Statement:
We need to save Order (master entity) and Order Details (salve entity) for this we can’t send data using JSON so we have to work with Tag Helper and HTML Helper.
Create Demo Project:
For demo we will be creating .Net Core 3.1 MVC project as follow.
Installing EF Core and DB Setup:
After creating Demo project let's create new entities create new class with name of Order and add following code in it.
public class Order
{
public Order()
{
Details = new List<OrderDetail>();
}
public int Id { get; set; }
[Required]
public string CustomerName { get; set; }
public DateTime Date { get; set; }
public decimal Total { get; set; }
public List<OrderDetail> Details { get; set; }
}
Also create entity for the Order Details as follow
public class OrderDetail
{
public int Id { get; set; }
public int OrderId { get; set; }
[Required]
public string ProductName { get; set; }
[Required]
public decimal Price { get; set; }
}
Now we are ready to setup our DB and EF Core for that install following EF nugget.
Install-Package Microsoft.EntityFrameworkCore -v 3.1.5
Install-Package Microsoft.EntityFrameworkCore.Design -v 3.1.5
Install-Package Microsoft.EntityFrameworkCore.SqlServer –v 3.1.5
Install-Package Microsoft.EntityFrameworkCore.Tools –v 3.1.5
.Now create Database context class with following code
public class AppDBContext : DbContext, IAppDbContext
{
public AppDBContext(DbContextOptions<AppDBContext> options)
: base(options)
{
}
public DbSet<Order> Orders { get; set; }
public DbSet<OrderDetail> OrdersDetails { get; set; }
public new async Task<int> SaveChanges()
{
return await base.SaveChangesAsync();
}
}
For detail under standing of EF core please refer to this article.
Add migration and run migration using following commands it will create table in Database
- add-migration SetupMigration
- update-Database
Create Order
Now let’s add new Action Method in “HomeController” named as Create with no argument as we know there will be no order with out any order details so we will create an order object and add an OrderDetil object in Order.Details and will pass it to view.
Now create a new view and write code for Order model as follow.
<form asp-action="Create" asp-controller="Home">
<div class="form-group">
<label for="txtCustomer">Customer Name</label>
<input type="text" class="form-control" id="txtCustomer" aria-describedby="emailHelp" placeholder="Customer Name" asp-for=CustomerName>
<input type="hidden" value="@DateTime.Now" asp-for=Date />
<input type="hidden" asp-for=Id />
<span class="txt-danger" asp-validation-for=CustomerName></span>
</div>
<div class="form-group">
<label for="txtTotal">Total</label>
<input type="text" class="form-control" id="txtTotal" placeholder="Total Order Amount" asp-for=Total>
<span class="txt-danger" asp-validation-for=Total></span>
</div>
<hr class="col-md-12" />
<div id="content-hldr">
<div class="row cln-det">
<div class="col-md-5">
<label>Product Name</label>
<input type="text" class="form-control cntrl" aria-describedby="emailHelp" placeholder="Prioduct Name" asp-for=Details[0].ProductName>
<input type="hidden" class="cntrl" asp-for=Details[0].Id />
<span class="txt-danger valid-cntrl" asp-validation-for=Details[0].ProductName></span>
</div>
<div class="col-md-5">
<label>Price</label>
<input type="text" class="form-control cntrl" aria-describedby="emailHelp" placeholder="Total Price" asp-for=Details[0].Price>
<span class="txt-danger valid-cntrl" asp-validation-for=Details[0].Price></span>
</div>
<div class="col-md-2">
<div> </div>
<button type="button" class="btn btn-primary btn-add-new">Add</button>
<button type="button" class="btn btn-danger btn-del">Del</button>
</div>
</div>
</div>
<hr class="col-md-12" />
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Now Write new post method for Create as follow
[HttpPost]
public async Task<IActionResult> Create(Order model)
{
model.Date = DateTime.Now;
if (ModelState.IsValid)
{
_appDbContext.Orders.Add(model);
await _appDbContext.SaveChanges();
return RedirectToAction("index");
}
return View();
}
It was pretty easy but wait there is an issue with that code you can’t add more than one record for Order details for adding more than one record we need to enable add button please add following JS Code in your file.
<script type="text/javascript">
var controllCount = 1;
$(document).on('click', '.btn-add-new', function(){
var clone = $('.cln-det').first().clone();
$('#content-hldr').append(clone);
controllCount++;
rename();
resetValidation();
})
$(document).on('click', '.btn-del', function(){
if(controllCount > 1){
$(this).closest('.cln-det').remove();
controllCount--;
rename();
resetValidation();
}
})
function resetValidation(){
var $form = $("form");
$form.unbind();
$form.data("validator", null);
$.validator.unobtrusive.parse(document);
}
function rename(){
var rows = $('.cln-det');
for(var i = 0; i < rows.length; i++){
var cntrls = $(rows[i]).find('.cntrl');
var validations = $(rows[i]).find('.valid-cntrl');
for(var j =0; j < cntrls.length; j++){
var nme = $(cntrls[j]).attr('name').split('.')[1];
$(cntrls[j]).attr('name','Details['+i+'].'+nme)
}
for(var j =0; j < validations.length; j++){
var nme = $(validations[j]).attr('data-valmsg-for').split('.')[1];
$(validations[j]).attr('data-valmsg-for','Details['+i+'].'+nme)
}
}
}
</script>
As we are cloning our Order detail div and appending that div to form so we can create as many details as we wish too. But there is a problem as we are using “Details[0].ProductName” in our Form so it will create html for only first item and we are cloning that HTML means there will be multiple HTML controls with same name (from my last post we know that ModelBinder use name of control for mapping data to c# object) so now Model Binder will get confuse and only last Order detail will pick and map for Order.Details.
As you can see there is a “rename” JS function in above code this function is always invoke on “Add” button click and just rename the controls name like “Details[1].ProductName”, “Details[2].ProductName” now model binder is good to go.
As from my last post we know that if we create new control using JS our client side validation will not work for newly created control for that we have a function “resetValidation” that rebind JQuery Validator Events so you don’t have to write your custom validation.
In next post we will see how we can edit order and order details. If you have any questions, please ask in comments I am always open to discuss.
You can download all code from github.
Comments / 0