How to MVC C# Viewmodel to join data from multiple tables - c#

Can anyone please tell me, I need to MVC C# Viewmodel join data from multiple tables and use chtml page #model ViewModels.StoreBrowseViewModel. But my logic will retrieve only one table data.
This is my class diagram. red box primary key, blue box foreign key
This is my StoreBrowseViewModel class
public class StoreBrowseViewModel
{
public int Id { get; set; }
public string Shape { get; set; }
public string Name { get; set; }
public string Clarity { get; set; }
public int CategoryID { get; set; }
public Category Category { get; set; }
public Shape Shapes { get; set; }
public IEnumerable<Gemstone> Gemstones { get; set; }
public IEnumerable<Clarity> Clarites { get; set; }
}
This is my action method.
public ActionResult Browse(string gemCategory = "")
{
var gemstones = from g in db.Gemstones
select g;
var category = db.Categories.Where(p => p.Name == gemCategory).FirstOrDefault();
gemstones = (gemstones.Include(s => s.Shapes)
.Include(c => c.Clarities)
.Where(p => p.CategoryID == category.CategoryID));
var viewModel = new StoreBrowseViewModel()
{
Category = category,
Gemstones = gemstones,
};
return this.View(viewModel);
}
This is my view model chtml page
#model ViewModels.StoreBrowseViewModel
grid.Column("Carat", header: "Weight " + Html.SortDirection(ref grid, "Carat")#item.Carat),
grid.Column("ShapeId", header: "Shape " + Html.SortDirection(ref grid, "Shape")#item.Shape),
grid.Column("ClarityId", header: "Clarity " + Html.SortDirection(ref grid, "Clarity")#item.Clarity),
grid.Column("Price", header: "Price(USD) " + Html.SortDirection(ref grid, "Price")#item.Price),
This is my out put It should display shape name and clarity name

I would do it differently from what im gona show below but this should help...
public ActionResult Browse(string gemCategory = "")
{
var category = db.Categories.FirstOrDefault(p => p.Name == gemCategory);
var gemstones = db.Gemstones.Include(s => s.Shapes)
.Include(c => c.Clarities)
.Include(c => c.Categories)
.Include(c => c.Cuts)
.Include(c => c.Orgins)
.Where(p => p.CategoryID == category.CategoryID);
var viewModel = new StoreBrowseViewModel() {Gemstones = gemstones};
return View(viewModel);
}
view model
public class StoreBrowseViewModel
{
public IEnumerable<Gemstone> Gemstones { get; set; }
}
in the view
#foreach(var item in Model.Gemstones)
{
<span>#item.Name</span>
#foreach(var item2 in Model.Gemstones.Clarities)
{
<span>#item2.Name</span>
}
}

Related

Selecting TreeView values based on a many to many relationships in a razor pages model

I have a Razor pages project that is trying to populate a Kendo TreeView (or any other TreeView) from a Database created with a Data Model.
The page I am working on contains apps that are in use, and the tree is reading a self referencing Organisations list so that we know what apps each organisation or department etc has access to.
I'm working on the Edit page in a razor app, so ~Pages\Apps\Edit.cshtml and ~Pages\Apps\Edit.cshtml.cs, with associated model pages as shown below.
These are the models involved, ignore RoleApps for this issue:
namespace FliveRetry.Models
{
public class Org
{
public int ID { get; set; }
public string OrgName { get; set; }
public int? ParentID { get; set; }
public bool? HasChildren { get; set; }
}
}
And
namespace FliveRetry.Models
{
public class App
{
public int ID { get; set; }
public string AppName { get; set; }
public string AppDescription { get; set; }
public int? DisplayOrder { get; set; }
public bool? Archived { get; set; }
public DateTime? Saved { get; set; }
public int? SavedBy { get; set; }
public ICollection<OrgAppJoin> OrgAppJoins { get; set; }
public ICollection<RoleAppJoin> RoleAppJoins { get; set; }
}
}
and the index model:
{
public class AppIndexData
{
public IEnumerable<App> Apps { get; set; }
public IEnumerable<Role> Roles { get; set; }
public IEnumerable<Org> Orgs { get; set; }
public IEnumerable<RoleAppJoin> RoleAppJoins { get; set; }
public IEnumerable<OrgAppJoin> OrgAppJoins { get; set; }
}
public class AssignedAppOrgData
{
public int OrgID { get; set; }
public string Org { get; set; }
public int? ParentID { get; set; }
public bool Assigned { get; set; }
public bool? HasChildren { get; set; }
}
public class SelectedAppOrgs
{
public int OrgID { get; set; }
}
public class SelectedAppOrgNames
{
public string OrgName { get; set; }
}
I have a page model to populate selected items into lists called selectedOrgs or selectedOrgNames that I can use in the view.
public class AppSelectPageModel : PageModel
{
//Load list for Selecting Orgs for Apps
public List<AssignedAppOrgData> AssignedAppOrgDataList;
public List<SelectedAppOrgs> selectedOrgs;
public List<SelectedAppOrgNames> selectedOrgNames;
public void PopulateAssignedAppOrgData(FliveRetryContext context, App app)
{
var allOrgs = context.Org;
var appOrgs = new HashSet<int>(
app.OrgAppJoins.Select(c => c.OrgID));
AssignedAppOrgDataList = new List<AssignedAppOrgData>();
selectedOrgs = new List<SelectedAppOrgs>();
selectedOrgNames = new List<SelectedAppOrgNames>();
foreach (var org in allOrgs)
{
AssignedAppOrgDataList.Add(new AssignedAppOrgData
{
OrgID = org.ID,
Org = org.OrgName,
Assigned = appOrgs.Contains(org.ID)
});
if (appOrgs.Contains(org.ID))
{
selectedOrgs.Add(new SelectedAppOrgs
{
OrgID = org.ID
});
selectedOrgNames.Add(new SelectedAppOrgNames
{
OrgName = org.OrgName
});
}
}
}
public void UpdateAppOrgs(FliveRetryContext context,
string[] selectedOrgs, App appToUpdate)
{
if (selectedOrgs == null)
{
appToUpdate.OrgAppJoins = new List<OrgAppJoin>();
return;
}
var selectedOrgsHS = new HashSet<string>(selectedOrgs);
var appOrgs = new HashSet<int>
(appToUpdate.OrgAppJoins.Select(c => c.Org.ID));
foreach (var org in context.Org)
{
if (selectedOrgsHS.Contains(org.OrgName.ToString()))
{
if (!appOrgs.Contains(org.ID))
{
appToUpdate.OrgAppJoins.Add(
new OrgAppJoin
{
AppID = appToUpdate.ID,
OrgID = org.ID
});
}
}
else
{
if (appOrgs.Contains(org.ID))
{
OrgAppJoin orgToRemove
= appToUpdate
.OrgAppJoins
.SingleOrDefault(i => i.OrgID == org.ID);
context.Remove(orgToRemove);
}
}
}
}
I then process them in OnGetAsync in Edit.cshtml.cs:
public async Task<IActionResult> OnGetAsync(int? id)
{
this.TreeData = GetOrgTreeData();
if (id == null)
{
return NotFound();
}
App = await _context.App
.Include(i => i.OrgAppJoins).ThenInclude(i => i.Org)
.Include(i => i.RoleAppJoins).ThenInclude(i => i.Role)
.AsNoTracking()
.FirstOrDefaultAsync(m => m.ID == id);
if (App == null)
{
return NotFound();
}
PopulateAssignedAppRoleData(_context, App);
PopulateAssignedAppOrgData(_context, App);
return Page();
}
and OnPostAsync
public async Task<IActionResult> OnPostAsync(int? id, string[] selectedOrgs, string[] selectedRoles)
{
if (!ModelState.IsValid)
{
return Page();
}
var appToUpdate = await _context.App
.Include(i => i.OrgAppJoins).ThenInclude(i => i.Org)
.Include(i => i.RoleAppJoins).ThenInclude(i => i.Role)
.FirstOrDefaultAsync(s => s.ID == id);
if (await TryUpdateModelAsync<App>(
appToUpdate,
"app", // Prefix for form value.
c => c.AppName, c => c.AppDescription, c => c.DisplayOrder))
{
UpdateAppOrgs(_context, selectedOrgs, appToUpdate);
UpdateAppRoles(_context, selectedRoles, appToUpdate);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
UpdateAppOrgs(_context, selectedOrgs, appToUpdate);
UpdateAppRoles(_context, selectedRoles, appToUpdate);
PopulateAssignedAppOrgData(_context, App);
PopulateAssignedAppRoleData(_context, App);
return Page();
}
This works fine when using multiselect dropdowns and reads and writes correctly to the many to many join tables.
I am using kendo controls at the moment, but I'm happy to use generic if I can find a solution to my problem.
I need to have a treeview for the Org model to display as a multi select, so I have two examples I am trying to get working, that behave differently, the DropDownTreeview is not essential but in some pages it will be handy, the TreeView is essential.
The first is the TreeView:
#(Html.Kendo().TreeView()
.Name("selectedOrgNames")
.DataTextField("OrgName")
.Checkboxes(checkboxes => checkboxes
.Name("ischecked")
.CheckChildren(true))
.HtmlAttributes(new { style = "width:100%" })
.DataSource(d => d
.Read(read =>
read.Url("/Apps/Edit?handler=Read")
)
)
)
The second is the DropDownTreeview:
#(Html.Kendo().DropDownTree()
.Placeholder("Select ...")
.Name("selectedOrgs")
.DataTextField("OrgName")
.DataValueField("ID")
.Checkboxes(checkboxes => checkboxes
.Name("ischecked")
.CheckChildren(true))
.AutoClose(false)
.Value(Model.selectedOrgNames)
.HtmlAttributes(new { style = "width:100%" })
.DataSource(d => d
.Read(read =>
read.Url("/Apps/Edit?handler=Read")
)
)
)
Both examples read the datasource from here in edit.cshtml.cs:
public IActionResult OnGetRead(int? id)
{
var result = from e in _context.Org
where id.HasValue ? e.ParentID == id : e.ParentID == null
select new
{
id = e.ID,
hasChildren = (from q in _context.Org
where (q.ParentID == e.ID)
select q
).Count() > 0,
OrgName = e.OrgName,
selected = (from s in _context.OrgAppJoin
where (s.OrgID == e.ID) && (s.AppID == 2)// <--this works, this doesn't--> (s.AppID == app.ID)
select s
).Count() > 0,
ischecked = (from s in _context.OrgAppJoin
where (s.OrgID == e.ID) && (s.AppID == 2)// <--this doesn't work, this doesn't either-->
(s.AppID == app.ID)
select s
).Count() > 0
};
return new JsonResult(result);
}
My first issue is probably very simple, I'm new to this platform: I can't seem to find a way to get the value of the AppID from the page into the OnGetRead Module ( i have hard coded s.AppID == 2 as an test example to see if it works) I have tried all sorts of variables and other methods.
The id passed into OnPostAsync and OnGetAsync is the id of the App, but the id passed into the OnGetRead is the id of the Org, which is correct and works, but how do I use the AppID from the page to replace the number two in this line? where (s.OrgID == e.ID) && (s.AppID == 2)?
My second issue is getting the checkboxes to read and write.
The DropDownTree above writes to database correctly, but doesn't read and populate checkboxes.
The Treeview doesn't populate checkboxes or write to the database, however it DOES read the selected value from the join table for app number 2 (or any other number I manually insert) in OnGetRead, and displays different font colours etc for the correct items as selected, but not as checked (aschecked may not even be a valid call theere, but I can't find a reference for that).
I feel like I'm close but I have tried for so long to get this to work to no avail, including to and fro with telerik who have tried help but they don't really help much with modelling to your own code, just with static data, and they seem to be light on for a Razor app knowledgebase and keep giving examples of controllers with hard coded data.
Please advise if I need to split this into two questions or more but any help will be much appreciated
Thanks

How to populate multiple tags into input form in ASP.NET

I have a form which has a place where a user can insert multiple tags separated by a comma into the database. I got it to insert, but I'm having trouble retrieving it to show on my edit form.
This is my Edit Action:
public IActionResult Edit(int id)
{
var gallery = _ctx.GalleryImages.SingleOrDefault(m => m.Id == id);
if (gallery == null)
return NotFound();
var categories = _ctx.Categories.ToList();
var model = new GalleryFormViewModel(gallery)
{
Tags = gallery.Tags,
Category = categories,
};
return View("Views/Image/UploadForm.cshtml", model);
}
Here is my ViewModel:
public class GalleryFormViewModel
{
public int? Id { get; set; }
public string Title { get; set; }
public IEnumerable<ImageTag> Tags { get; set; }
public IEnumerable<Category> Category { get; set; }
[Required]
public int CategoryId { get; set; }
public IFormFile ImageUplaod { get; set; }
public GalleryFormViewModel()
{
Id = 0;
}
public GalleryFormViewModel(GalleryImage galleryImage)
{
Id = galleryImage.Id;
Title = galleryImage.Title;
Tags = galleryImage.Tags;
CategoryId = galleryImage.CategoryId;
}
}
And here is the Form input: (I'm using this form for creating and editing the gallery)
<div class="form-group">
#Html.LabelFor(m => m.Tags)
#Html.TextBoxFor(m => m.Tags, new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.Tags)
</div>
Here is the Tag Model:
namespace SimpleImageGallery.Data.Models
{
public class ImageTag
{
public int Id { get; set; }
public string Description { get; set; }
}
}
Here is the Gallery Model:
public class GalleryImage
{
public virtual IEnumerable<ImageTag> Tags { get; set; }
// ....
}
This is how the tags table looks in the database:
It seems like I'm not getting any errors, maybe something is wrong in the actual input field?
There are some mistakes :
First, you have to Include the Tags to retrieve them from DB (if using Entity Framework):
var gallery = _ctx.GalleryImages.Include(m=>m.Tags).SingleOrDefault(m => m.Id == id);
Secondly, you are doing the same this twice :
var model = new GalleryFormViewModel(gallery)
{
Tags = gallery.Tags,
Category = categories,
};
and
public GalleryFormViewModel(GalleryImage galleryImage)
{
Id = galleryImage.Id;
Title = galleryImage.Title;
Tags = galleryImage.Tags;
CategoryId = galleryImage.CategoryId;
}
Thirdly, you cannot do this : #Html.TextBoxFor(m => m.Tags, new { #class = "form-control" }) for a enumerable, you have to reconstruct the string.

DbEntityValidationException When Inserting Record

Sorry for the very long post. I have these table relationships:
- Room has many-to-many relationship with Activity
- RoomActivity has one-to-many relationship with Room and Activity
- Item has many-to-many relationship with Part
- ItemPart has one-to-many relationship with Item and Part
- Stage has foreign key relationship to RoomActivity and ItemPart
- Submission has a foreign key relationship to Stage
I have a web application where employees can submit what they do today, which consist of Room, Activity, Item, Part then submit the form.
My Controller:
public ActionResult Create()
{
ViewBag.ActivityRejectCodeId = GetRejectCodesByActivity(0);
ViewBag.Activities = GetActivities();
ViewBag.Workstations = GetRooms();
ViewBag.Platforms = GetItems();
ViewBag.Parts = GetParts();
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(WorkOrderSubmission workordersubmission)
{
if (ModelState.IsValid)
{
var activityId = Int32.Parse(Request.Form["Stage.RoomActivity.Activity.Id"]);
var workstationId = Int32.Parse(Request.Form["Stage.RoomActivity.Workstation.Id"]);
var platformId = Int32.Parse(Request.Form["Stage.ItemPart.Platform.Id"]);
var partId = Int32.Parse(Request.Form["Stage.ItemPart.Part.Id"]);
var rs = (from ps in db.Stages
join wa in db.RoomActivities on ps.RoomActivityId equals wa.Id
join pp in db.ItemParts on ps.ItemPartId equals pp.Id
where ps.RoomActivity.ActivityId == activityId
&& ps.RoomActivity.RoomId == workstationId
&& ps.ItemPart.ItemId == platformId
&& ps.ItemPart.ItemId == partId
select new { ps.Id }).FirstOrDefault();
var stageId = rs.Id;
workordersubmission.StageId = stageId;
workordersubmission.SubmissionDate = DateTime.Now;
// Error when saving here
db.WorkOrderSubmissions.Add(workordersubmission);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.stageId = new SelectList(db.Stages.OrderBy(p => p.Name), "Id", "Name", workordersubmission.StageId);
return View(workordersubmission);
}
public SelectList GetActivities()
{
var results = (from ps in db.Stages
join wa in db.RoomActivities on ps.RoomActivityId equals wa.Id
join a in db.Activities on wa.ActivityId equals a.Id
select new
{
Id = wa.ActivityId,
Name = a.Name
})
.Distinct()
.OrderBy(n => n.Name);
return new SelectList(results, "Id", "Name");
}
My View:
<div class="editor-label">
#Html.LabelFor(model => model.Stage.RoomActivity.Activity.Id, "Activity")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Stage.RoomActivity.Activity.Id,
(SelectList)ViewBag.Activities, "")
#Html.ValidationMessageFor(model => model.Stage.RoomActivity.Activity.Id)
</div>
I'm getting the error below(Updated):
Validation failed for entity [Part]. Validation errors:
Number: The Number field is required.
Validation failed for entity [Item]. Validation errors:
Name: The Name field is required.
Validation failed for entity [Activity]. Validation errors:
Name: The Name field is required.
Validation failed for entity [Room]. Validation errors:
Name: The Name field is required
Why do I get the error on Part, Item, Activity, Room? I'm trying to insert a new Submission.
Stage model:
public partial class Stage
{
public Stage()
{
this.WorkOrderSubmissions = new HashSet<WorkOrderSubmission>();
}
public int Id { get; set; }
public int RoomActivityId { get; set; }
public int ItemPartId { get; set; }
public string Description { get; set; }
public string Name { get; set; }
public virtual ItemPart ItemPart { get; set; }
public virtual RoomActivity RoomActivity { get; set; }
public virtual ICollection<WorkOrderSubmission> WorkOrderSubmissions { get; set; }
}
WorkOrderSubmission model:
public partial class WorkOrderSubmission
{
public int Id { get; set; }
public int WorkOrderId { get; set; }
public int StageId { get; set; }
public System.DateTime SubmissionDate { get; set; }
public virtual Stage Stage { get; set; }
public virtual WorkOrder WorkOrder { get; set; }
}
Could you add this code to get more information about DbEntityValidationException and show it?
try
{
db.SaveChanges();
}
catch (DbEntityValidationException e)
{
string errorFormat = #"Validation failed for entity [{0}]. Validation errors:" + Environment.NewLine + #"{1}";
var errorList = new List<String>();
foreach (var entityValidationError in e.EntityValidationErrors)
{
var entityName = entityValidationError.Entry.Entity.GetType().Name;
var errors = String.Join(Environment.NewLine, entityValidationError.ValidationErrors.Select(a => a.PropertyName + ": " + a.ErrorMessage));
errorList.Add(String.Format(errorFormat, entityName, errors));
}
throw new Exception(String.Join(Environment.NewLine + Environment.NewLine, errorList) + Environment.NewLine, e);
}

How to return two models in MVC?

I have this function that will be called in the controller:
public EditViewModel PostEditViewModel(EditViewModel model)
{
using (var db = new NorthwindEntities())
{
var prod = db.Products.Where(x => x.Id == model.Id).Single();
{
prod.Id = model.Id;
...
//I need something like this:
//prod.CategoryID = model.CategoryList.CatId
//but obviously intellisense tells me that after the dot of CategoryList, only methods of that list can be called.
db.SaveChanges();
}
and this is my ViewModel:
public int Id{ get; set; }
...
public IEnumerable<Categories> CategoryList { get; set; }
public class Categories {
public int ProdId { get; set; }
public int? CatId { get; set; }
public string CatName { get; set; }
}
how do I call the CategoryList through my EditViewModel so that I can edit the Category of a specific product through HTML.DropdownList?
If your product model have CategoryId property (I just can't see it in your question) and you using strongly typed View You always can use this overload of DropDownListBoxFor() helper:
#Html.DropDownListFor(
x => x.CategoryId,
new SelectList(Model.CategoryList, "CatId", "CatName")
)
But actually i recomend you to use SelectListItem in ViewModels for all your dropdowns, becouse it's really bad practice - put domain entity on your View
Than your ViewModel will be like:
public int Id { get; set; }
public int CategoryId { get; set; }
...
public IEnumerable<SelectListItem> CategoryList { get; set; }
And on the View you can do this way:
#Html.DropDownListFor(x => x.CategoryId, Model.CategoryList)
In your GET ViewModel Controller you can initialize your CategoryList like this:
model.CategoryList = db.Categories.OrderBy(x => x.Name)
.Select(x => new SelectListItem
{
Text = x.Name,
Value = x.Id.ToString()
});
It really helps you to get your Views cleaner.
It looks like CategoryList is used to populate the items in your DropDownList, and CatId is the property on your view model that captures the ID value of the selected category.
If that is the case, you can just assign it like so:
if (model.CatId.HasValue)
{
prod.CategoryID = model.CatId.Value;
}
If I not mistaken and I understood you, you should to create class EditViewModel in this class create the fields:
public int Id{ get; set; }
...
public IEnumerable<Categories> CategoryList { get; set; }
Next, in your controller, you should use the following code:
var prod = db.Products.Where(x => x.Id == model.Id).Single();
{
prod.Id = model.Id;
...
prod.CategoryID = model.CategoryList.Select(m => m.CatId)
//but Select returned the List of CatId, I suggest thet prod.CategoryID is List
}
db.SaveChanges();

How to select from two table by id in mvc5

I'm new to MVC5,I had troubles with the problem.
can anybody help me?
I have 2 table, DocMain(Doc_Id,Doc_Title) , DocProduct(Doc_Id,Doc_Content),
I want to select the 2 table content by the same Doc_Id.
And loop them.
Display like:
<ul>
<li>title1content1</li>
<li>title2content2</li>
<li>title3content3</li>
</ul>
....
And how to do it?
//Here is my viewmodel
public class MainProductViewModel
{
public IEnumerable<DocMainListView> DocMainListView { get; set; }
public IEnumerable<DocProductListView> DocProductListView { get; set; }
}
-------------------------------------------------
//Here is my controller
public class DocProductController : Controller
{
private IDocProductRepository repository;
private IDocMainRepository repositoryMain;
public DocProductController(IDocProductRepository docProductRepository, IDocMainRepository docMainRepository)
{
this.repository = docProductRepository;
this.repositoryMain = docMainRepository;
}
public ActionResult List()
{
var products = from docProduct in repository.DocProduct
join docMain in repositoryMain.DocMain
on docProduct.Doc_Id equals docMain.Doc_Id
select new { DocMainTitle = docMain.Doc_Title, DocProductContent = docProduct.DocProduct_Content };
//ViewBag.products = products;
//DocProductListView model = new DocProductListView
//{
// DocProduct = repository.DocProduct
// .Join(repositoryMain.DocMain,
// docProduct => docProduct.Doc_Id,
// docMain => docMain.Doc_Id,
// (docProduct, docMain) => new { a = docMain.Doc_Id, b = docProduct.Doc_Id })
// .OrderByDescending(n => n.)
//};
return View(products);
}
}
I don't know how to write the controller code and View code.
As you want to display the title and content only, so your view model would be
public class MainProductViewModel
{
public IEnumerable<ProductInfo> Products { get; set;}
}
public class ProductInfo
{
public string DocMainTitle { get; set;}
public string DocProductContent { get; set;}
}
And your query would be:
var products = from docProduct in repository.DocProduct
join docMain in repositoryMain.DocMain
on docProduct.Doc_Id equals docMain.Doc_Id
select new ProductInfo { DocMainTitle = docMain.Doc_Title, DocProductContent =
docProduct.DocProduct_Content };
And assign this products to the Products of MainProductViewModel and return to view, then config your view as
#model MainProductViewModel

Categories