Many to Many crud operations in asp.net core - asp.net

I have two entities (Product and Supply) that have a many-to-many relationship. I also have an entity between then that holds the two ID's (SupplyProduct).
My entities:
public class Product
{
[Key]
public int ProductId { get; set; }
[Required]
public string? ProductName { get; set; }
[Required]
[Column(TypeName = "decimal(6,2)")]
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public string? Brand { get; set; }
public DateTime CreatedDateTime { get; set; } = DateTime.Now;
//Many to many relationship between the products and the stocks
public virtual ICollection<SupplyProduct>? SupplyProducts { get; set; }
}
public class Supply
{
[Key]
[Required]
public int SupplyId { get; set; }
[Required]
[DisplayName("Supply's Label")]
public string? Label { get; set; }
//One to many relationship between the Stock and the Merchant
public Merchant? Merchant { get; set; }
//Many to many relationship between the stocks and the products
public virtual ICollection<SupplyProduct>? SupplyProducts { get; set; }
}
public class SupplyProduct
{
[Key]
public int SupplyId { get; set; }
public virtual Supply? Supply { get; set; }
[Key]
public int ProductId { get; set; }
public virtual Product? Product { get; set; }
}
I want to assign a supply to a product while creating it . and then show the supply with it's associated products
this is my products controller:
ProductsController.cs
public class ProductController : Controller
{
private readonly ApplicationDbContext _db;
public ProductController(ApplicationDbContext db)
{
_db = db;
}
// GET: ProductController
public ActionResult Index()
{
IEnumerable<Product> ProductsList = _db.Products;
return View(ProductsList);
}
// GET: ProductController/Create
public ActionResult Create()
{
IEnumerable<Supply> SuppliesList = _db.Supplies.Include(s => s.Merchant);
ViewBag.Supplies = SuppliesList;
return View();
}
// POST: ProductController/Create
[HttpPost]
public ActionResult Create(Product model, List<int> supplyIds)
{
_db.Products.Add(model);
_db.SaveChanges();
SupplyProduct SP = new();
foreach (var supplyId in supplyIds)
{
SP.SupplyId = supplyId;
SP.ProductId = model.ProductId;
SP.Product = model;
SP.Supply = _db.Supplies.Where(x => x.SupplyId == supplyId).FirstOrDefault();
}
_db.SupplyProducts.Add(SP);
_db.SaveChanges();
return RedirectToAction(nameof(Index));
}
}
Can you please check my post Create method if it is as it should be, and how can I get the Products data while returning the Supplies in the Index method into the index view?
Thank you so much for your help and happy coding :D

Can you please check my post Create method if it is as it should be
Modify your code like below, otherwise you will always store the second supply in supplyIds:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Product model, List<int> supplyIds)
{
_context.Product.Add(model);
_context.SaveChanges();
SupplyProduct SP = new();
foreach (var supplyId in supplyIds)
{
SP.SupplyId = supplyId;
SP.ProductId = model.ProductId;
SP.Product = model;
SP.Supply = _context.Supply.Where(x => x.SupplyId == supplyId).FirstOrDefault();
_context.SupplyProducts.Add(SP); //move to here...
_context.SaveChanges();
}
// _context.SupplyProducts.Add(SP);
//_context.SaveChanges();
return RedirectToAction(nameof(Index));
}
how can I get the Products data while returning the Supplies in the Index method into the index view?
Change your Index method like below:
// GET: Products
public async Task<IActionResult> Index()
{
var data = await _context.Product.Include(p => p.SupplyProducts)
.ThenInclude(sp => sp.Supply).ToListAsync();
return View(data);
}

You can remove the SupplyProduct tabble if there are no additional properties in anything other than Supply Product you don't need it for many-to many.
Then initialize the collections in the Supply and Product
public class Product
{
public Product()
{
this.Supplys = new HashSet<Supply>();
}
//... your props
public virtual ICollection<Supply> Supplys { get; set; }
}
public class Supply
{
public Supply()
{
this.Products = new HashSet<Product>();
}
//... your props
public virtual ICollection<Product> Products { get; set; }
}
Add Product to Supplys with only one query (in your code you make query for everyone Id in supplyIds)
[HttpPost]
public ActionResult Create(Product model, List<int> supplyIds)
{
//Get all supplys you need by id
var supplys = _db.Supplys
.Where(x => supplyIds.Contains(x.SupplyId))
.ToList();
//Add product in each supply
foreach (var supply in supplys)
{
supply.Products.Add(model);
}
//Update db
_db.SaveChanges();
return RedirectToAction(nameof(Index));
}
Get from DB
public ActionResult GetSuplys(List<int> supplyIds)
{
//Here you get all Supplys with the Products in it
var supplys = _db.Supplys
.Include(x => x.Products)
.Where(x => supplyIds.Contains(x.SupplyId))
.ToList();
//...
}
Save new Supply of Product
public ActionResult NewSuply()
{
var supply = new Supply
{
ProductName = name,
//Add all props you need
//You can add Product here or add empty collection
Products.Add(product), or = new List<Product>();
}
//No need to save Product separate
_db.Add(supply);
_db.SaveChanges();
}

Related

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.ConsoleUserInfoes_dbo.ConsolesCheckBoxes_consoleId"

I'm getting this error:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.ConsoleUserInfoes_dbo.ConsolesCheckBoxes_consoleId". The conflict occurred in database "aspnet-ForePlay-20180525122039", table "dbo.ConsolesCheckBoxes", column 'ConsoleId'.
I'm using Entity Framework and ASP.NET MVC 5 and IdentityUser and try to insert data form checkListBox to table into my database.
This is happening on the register view, when user need to register and fill the form.
public class ConsoleUserInfo
{
[Key]
public int identity { get; set; }
[Required]
[StringLength(255)]
[ForeignKey("User")]
public string userid { get; set; }
[Required]
[ForeignKey("consolesCheckBox")]
public int consoleId { get; set; }
public virtual ApplicationUser User { get; set; }
public virtual ConsolesCheckBox consolesCheckBox { get; set; }
}
This is the table that need to get a user id (form applictionUser) and consoleId
(form ConsolesCheckBox )
This is the ApplicationUserUser model class:
public class ApplicationUser : IdentityUser
{
[Required]
[StringLength(255)]
override
public string UserName { get; set; }
[Required]
[StringLength(50)]
public string Phone { get; set; }
public byte[] UserPhoto { get; set; }
public virtual UserAddress Address { get; set; }
public virtual ICollection<ConsolesCheckBox> consoleCheckBox { get; set; }
}
and this is the checkBoxList table:
public class ConsolesCheckBox
{
[Key]
public int ConsoleId { get; set; }
public string ConsoleName { get; set; }
public bool IsChecked { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUser { get; set; }
}
This is my account controller, all in the register get and post
// GET: /Account/Register
[AllowAnonymous]
public ActionResult Register()
{
//using database
using (ApplicationDbContext dbo = new ApplicationDbContext())
{
//data will save list of the consoleCheckBoxItem
var data = dbo.consolesCheckBox.ToList();
// because the view is request a common model, we will create new one
CommenModel a = new CommenModel();
a.ConsolesCheckBoxList = data;
// we will need to return common model, that way we will return a
return View(a);
}
}
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register([Bind(Exclude = "UserPhoto")]CommenModel model)
{
if (ModelState.IsValid)
{
// To convert the user uploaded Photo as Byte Array before save to DB
byte[] imageData = null;
if (Request.Files.Count > 0)
{
HttpPostedFileBase poImgFile = Request.Files["UserPhoto"];
using (var binary = new BinaryReader(poImgFile.InputStream))
{
imageData = binary.ReadBytes(poImgFile.ContentLength);
}
}
var user = new ApplicationUser
{
UserName = model.registerViewModel.Email,
Email = model.registerViewModel.Email,
Phone = model.registerViewModel.Phone
};
user.UserPhoto = imageData;
var result = await UserManager.CreateAsync(user, model.registerViewModel.Password);
//after the user create, we will use the id and add the id to the userAddress table include
// Address, longitude and latitude.
using (ApplicationDbContext dbo = new ApplicationDbContext())
{
var currentUserId = user.Id;
var pasinfo = dbo.userAddress.FirstOrDefault(d => d.Userid == currentUserId);
if (pasinfo == null)
{
pasinfo = dbo.userAddress.Create();
pasinfo.Userid = currentUserId;
dbo.userAddress.Add(pasinfo);
}
pasinfo.Address = model.useraddress.Address;
pasinfo.latitude = model.useraddress.latitude;
pasinfo.longitude = model.useraddress.longitude;
dbo.SaveChanges();
foreach (var item in model.ConsolesCheckBoxList.Where(x => x.IsChecked).Select(x => x.ConsoleId))
{
var consoleUserInfo = new ConsoleUserInfo
{
userid = currentUserId,
consoleId = item
};
dbo.consoleUserInfo.Add(consoleUserInfo);
}
dbo.SaveChanges();
}
}
}
In the register GET I have a common model, because I used 3 models in the view
this is the common model:
public class CommonModel
{
public UserAddress useraddress { get; set; }
public RegisterViewModel registerViewModel { get; set; }
public List<ConsolesCheckBox> ConsolesCheckBoxList { get; set; }
}
I need your help here, I've been trying to fix this all day.

Allow user to edit list items in MVC?

I'm using Entity Framework Core to build a simple web app. For this app, I've created a model called Company that includes basic business info + a list of contacts (sales reps).
Here's my model:
public class Company
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
public string Promo { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
public class Contact
{
[Key]
public int ContactID { get; set; }
[ForeignKey("Company")]
public int CompanyID { get; set; }
public virtual Company Company { get; set; }
public string ContactName { get; set; }
public string ContactNumber { get; set; }
}
Here's the controller's index() method:
// GET: Companies
public async Task<IActionResult> Index()
{
List<Company> viewModelData = await _context.Companies
.Include(c => c.Contacts)
.ToListAsync();
return View(viewModelData);
}
Edit method:
// GET: Companies/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var company = await _context.Companies
.Include(v => v.Contacts)
.FirstOrDefaultAsync(m => m.ID == id);
if (company == null)
{
return NotFound();
}
return View(company);
}
// POST: Companies/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, [Bind("ID,Name,Promo,Contacts")] Company company)
{
if (id == null)
{
return NotFound();
}
var companyToUpdate = await _context.Companies
.Include(v => v.Contacts)
.FirstOrDefaultAsync(m => m.ID == id);
if (await TryUpdateModelAsync<Company>(
companyToUpdate,
"",
i => i.Name, i => i.Promo, i => i.Contacts
)) {
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException /* ex */)
{
//Log the error (uncomment ex variable name and write a log.)
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction("Index");
}
return View(companyToUpdate);
}
This is not correct since the code only allows me to edit Company info. How do I modify the code so that I can edit both Company & its contacts on the same edit view?
If you're purely looking to update values, then you can explicitly update them like so. A View Model is also recommended but this comes down to good vs bad practice. This omits the exception handling and serves only as an example of how to map these values, you'll have to modify the remainder of your controller to work directly with the CompanyEditViewModel
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int? id, [Bind("ID,Name,Promo,Contacts")] CompanyEditViewModel company)
{
if (!ModelState.IsValid)
return RedirectToAction("Index");
var companyToUpdate = await _context.Companies
.Include(v => v.Contacts)
.FirstOrDefaultAsync(m => m.ID == id);
// Assign the new values
companyToUpdate.Name = company.Name;
companyToUpdate.Promo = company.Promo;
companyToUpdate.Contacts = company.Contacts?.ToList();
// Update and save
_context.Companies.Update(companyToUpdate);
await _context.SaveChangesAsync();
return View(companyToUpdate);
}
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public string Promo { get; set; } // Yes or No field
public List<Contact> Contacts { get; set; }
public class Contact
{
[Key]
public int ContactID { get; set; }
public int CompanyID { get; set; }
public string ContactName { get; set; }
public string ContactNumber { get; set; }
}
}
// The View Model contains the Company details which were modified
// The first Edit method will have to be updated to bind this View Model to the view
public class CompanyEditViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public string Promo { get; set; }
public IList<Company.Contact> Contacts { get; set; }
}

How to join my tables with identity tables?

I started a default MVC project with Identity and EF.
In my app users will be able to create and edit some records.
In the table for these records, I want to have the ids of users who created the record and who updated lastly.
My model class is like:
public class Record
{
public int ID { get; set; }
public DateTime CreateTime { get; set; }
public string CreatingUserID { get; set; }
public string UpdatingUserID { get; set; }
public DateTime UpdateTime { get; set; }
public Enums.RecordStatus Status { get; set; }
}
And in RecordsController, I save new records to db like this:
[Authorize]
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(FormCollection form, RecordCreateVM vm)
{
string userId = User.Identity.GetUserId();
DateTime now = DateTime.Now;
Record rec = new Record ();
if (ModelState.IsValid)
{
int newRecordId;
using (RecordRepository wr = new RecordRepository())
{
UpdateModel(rec);
rec.CreateTime = now;
rec.UpdateTime = now;
rec.CreatingUserID = userId;
rec.UpdatingUserID = userId;
rec.Status = Enums.RecordStatus.Active;
Record result = wr.Add(rec);
wr.SaveChanges();
newRecordId = result.ID;
}
}
}
When I am listing these records, I also want my page to display these users' usernames.
I get all the active records from the repository I created.
public ActionResult Index()
{
RecordListVMviewModel = new RecordListVM();
using (RecordRepository wr = new (RecordRepository())
{
viewModel.Records = wr.GetAll();
}
return View(viewModel);
}
And this is the repository code:
public class RecordRepository: Repository<Record>
{
public override List<Record> GetAll()
{
IQueryable<Record> activeRecords = DbSet.Where(w => w.Status == Enums.RecordStatus.Active);
return activeRecords.ToList();
}
}
Where do I have to make changes? Can you give me an sample code for usages like this?
Thank you.
You need to change
public string CreatingUserID { get; set; }
public string UpdatingUserID { get; set; }
to something like:
public User CreatingUser { get; set; }
public User UpdatingUser { get; set; }
Set the ID's during the creation of new RecordRepository()
Then access them as Record.CreatingUser.FirstName ect

Send info to the view from another table with navigation property?

This is my models
public class Product
{
[Key]
public int SerialNumber { get; set; }
public int PartNumber { get; set; }
public string Description { get; set; }
public virtual ICollection<Reading> Reading { get; set; }
}
public class Reading
{
[Key]
public int Id { get; set; }
public int SerialNumber { get; set; }
public int ReadingValue { get; set; }
public virtual Product Product { get; set; }
}
I can send all products to the view with
return View(db.Products.ToList().Where(product => product.CustomerID == Customer));
And I can get the latest ReadingValue if I know the Product SerialNumber
var LatestReading = db.Readings.OrderByDescending(m => m.Id).Where(s => s.SerialNumber == SerialNumber).Select(m => m.ReadingValue).FirstOrDefault();
How can I send all the products to the view with the latest ReadingValue for each product?
Create a new view model that will hold both the data:
public class FooViewModel
{
public List<Product> Products { get; set; }
public Reading LatestReading { get; set; }
}
Change your view to use the new model with:
#model FooViewModel
Then send them back in your controller:
var model = new FooViewModel();
model.Products = db.Products.ToList().Where(product => product.CustomerID == Customer);
model.LatestReading = db.Readings.OrderByDescending(m => m.Id).Where(s => s.SerialNumber == SerialNumber).Select(m => m.ReadingValue).FirstOrDefault();
return View(model);
Because you have Reading property in Products class, you can get the latest ReadingValue in the view:
foreach(Product product in Model)
{
var latestReadingValue = product.Reading.OrderByDescendin(m => m.Id).FirstOrDefault();
// do what you want here
}
but as hutchonoid points out the better option is creating a ViewModel for it, because having logic in the view is a bad practice, and it doesn't correspond to MVC pattern.

Mvc4 how can i use 2 tables in a single view

This is an ASP.NET MVC4 project with Entity Framework.
I need to use 2 tables in single view
I have 2 tables in my database (Budgets and Products).
My Models
Budget.cs
public class Budget
{
public int Id { get; set; }
public string Title { get; set; }
public ICollection<Product> Products { get; set; }
}
Product.cs
public class Product
{
public int Id { get; set; }
public string Title { get; set; }
public decimal Value { get; set; }
public int BudgetId { get; set; }
}
BpDb
public class BpDb:DbContext
{
public DbSet<Budget> Budgets { get; set; }
public DbSet<Product> Products { get; set; }
}
MasterBP
public class MasterBP
{
public int Id { get; set; }
public string Title { get; set; }
public List<Product> ProdName { get; set; }
public int CountOfValues { get; set; }
}
I need something about Controller to get ProdName (Title from table products)
public class HomeController : Controller
{
BpDb _db = new BpDb();
public ActionResult Index()
{
var model=_db.Budgets
.Select(r=>new MasterBP
{
Id = r.Id,
Title = r.Title,
ProdName = r.Products.Where(s => s.BudgetId == r.Id).ToList(),
CountOfValues = r.Products.Count()
});
return View(model);
}
}
I need a way to get a list of products Title when the BudgetId = ID (1 Budget can have more than 1 Products)
And my Index is like:
#model IEnumerable<MvcGg.Models.MasterBP>
#foreach (var item in Model)
{
<div>
<h4>#item.Title</h4>
#foreach (var product in item.ProdName)
{
#(product.Title.ToString());
}
Values:#item.CountOfValues
</div>
}
It sounds like (correct me if I am wrong) you want to access two sets of entities in your view model?
Then something along these lines will do:
public class BudgetsViewModel
{
public IEnumerable<MasterBP> MasterBps { get;set; }
public IEnumerable<Product> Products { get;set;}
}
And then you just retrieve all the data and populate the viewmodel and pass it down.
...
var budgets =_db.Budgets
.Select(r=>new MasterBP
{
Id = r.Id,
Title = r.Title,
ProdName = r.Products.Where(s => s.BudgetId == r.Id).ToList(),
CountOfValues = r.Products.Count()
});
var model = new BudgetsViewModel()
{
MasterBps = budgets
//Products here
}
return View(model);
...
However it would be better if you made some form of service layer to handle the retrieving on entities and the mapping to view models instead of having all this logic in your controller.
finally this solve my problem,
Model:
MasterBP
public class MasterBP
{
public int Id { get; set; }
public string Title { get; set; }
public IEnumerable<Product> ProdName { get; set; }
public int CountOfValues { get; set; }
}
I replaced List<> with IEnumerable<>
Controller:
public ActionResult Index()
{
var model=_db.Budgets
.Select(r=>new MasterBP
{
Id = r.Id,
Title = r.Title,
ProdName = r.Products.Where(s => s.BudgetId == r.Id).AsEnumerable(),
CountOfValues = r.Products.Count()
});
return View(model);
}
In addition i have change ToList() in AsEnumerable() and now i can see my result using this
View:
Index
#foreach (var item in Model)
{
<div>
#item.Title
<ul>
#foreach (var prod in item.ProdName)
{
<li>#prod.Title</li>
}
</ul>
<hr />
</div>
}

Resources