Details' View with related data via LINQ using a custom model - c#

Since I am new to ASP, sure am puzzled how to have a List of some specific columns of a model/table, derived via LINQ, be reflected on the relevant parent item's Details page.
Consider the example of two classes like:
public class Tournament
{
[Key]
public string TournamentID { get; set; }
public DateTime TournamentDate { get; set; }
public string Place { get; set; }
[ForeignKey("TeamA")]
public string TeamAID { get; set; }
public Team TeamA { get; set; }
[ForeignKey("TeamB")]
public string TeamBID { get; set; }
public Team TeamB { get; set; }
}
public class Team
{
[Key]
public string TeamID { get; set; }
public string TeamName { get; set; }
public string Captain { get; set; }
[InverseProperty("TeamA")]
public ICollection<Tournament> TeamA { get; set; }
[InverseProperty("TeamB")]
public ICollection<Tournament> TeamB { get; set; }
}
The two tables have the details of Teams, and Tournaments played between them. Since the Tournament's model/table has more than one of it's field connected to the Team, the InverseProperty & relevant ForeignKeys are being used.
The main object is to present on a View a part of the details of a Team on the top but the relevant entries of the Tournaments being listed below the same.
Since a team, for example Team_1 might be existing in the TeamA column of the Tournament or even in the TeamB column, the question pops up as to how to have the same be presented in a manner like:
TeamID: ID_1
TeamName: Team_1
TeamCaptain: Captain1
ID | Date | Competitor | Place
.... | ........ | ................ | .........
.... | ........ | ................ | .........
For the same reason I came to deduce that I should use some special Model Class for the Custom Columns and thus added the following class:
public class GamesList
{
public string TID { get; set; }
public DateTime TDate { get; set; }
public string TPlace { get; set; }
public string TeamLinks { get; set; }
public string TeamNames { get; set; }
}
and a controller action like:
public async Task<IActionResult> Index2(string teamId)
{
var gamesList = ((from x in _context.Tournaments
.Where(x => x.TeamAID == teamId)
select new GamesList
{
TID = x.TournamentID,
TDate = x.TournamentDate,
TeamLinks = x.TeamAID,
TeamNames = x.TeamB.TeamName,
TPlace = x.Place
})
.Concat(from x in _context.Tournaments
.Where(x => x.TeamBID == teamId)
select new GamesList
{
TID = x.TournamentID,
TDate = x.TournamentDate,
TeamLinks = x.TeamBID,
TeamNames = x.TeamA.TeamName,
TPlace = x.Place
})).OrderBy(x => x.TDate);
return View(await gamesList.ToListAsync());
}
Thus, to have the same list being concatenated but with Team IDs & Names flipped, resulting in all the IDs compiled in a property named TeamLinks while names of the competitors lined up in the TeamNames.
Now, the said list can be presented by having a #model IEnumerable but as the main goal specified how to present such a list under the Details of the Team (i.e. the one also possessing the TeamID == teamId)?
Thanks.

You need to start with a view model(s) that represents what you want in the view
public class TeamVM
{
public string ID { get; set; }
public string Name { get; set; }
public string Captain { get; set; }
public IEnumerable<TournamentVM> Tournaments { get; set; }
}
public class TournamentVM
{
public string ID { get; set; }
public DateTime Date { get; set; }
public string Place { get; set; }
public string Competitor { get; set; }
}
Then query you database to get the team (by its TeamID) including the collections of Tournament and map the result to your view models. Using LinqToEntities
Team team = db.Teams.Where(x => x.TeamID == teamId).FirstOrDefault();
if (team == null) { ... }; // error
TeamVM model = new TeamVM
{
ID = team.TeamID,
Name = team.TeamName,
Captain = team.Captain,
// join the collections into a new collection of TournamentVM
Tournaments = team.TeamA.Where(x => x.TeamAID == team.TeamID).Select(x => new TournamentVM
{
ID = x.TournamentID,
Date = x.TournamentDate,
Place = x.Place,
Competitor = x.TeamB.TeamName
}).Concat(team.TeamB.Where(x => x.TeamBID == team.TeamID).Select(x => new TournamentVM
{
ID = x.TournamentID,
Date = x.TournamentDate,
Place = x.Place,
Competitor = x.TeamA.TeamName
})).OrderBy(x => x.Date)
};
return View(model);
and in the view
#model TeamVM
....
<div>#Model.ID</div>
....
<table>
<thead> ... </thead>
<tbody>
#foreach(var item in Model.Tournaments)
{
<tr>
<td>#item.ID</td>
<td>#item.Date</td>
....
</tr>
}
</tbody>
</table>

Related

Display table with category name in one column and list of items belonging to category in second column with links on Details View in ASP.NET MVC View

I my ASP.NET MVC Core 3.1 app I have Model that stores professions which looks like this:
public partial class Professions
{
public int ProfessionID { get; set; }
public int? ProfessionGroupID { get; set; }
public string ProfessionTitle { get; set; }
public string ProfessionDescription { get; set; }
}
So each profession belongs to some group of professions, therefore Model for profession groups looks like this:
public partial class ProfessionGroups
{
public ProfessionGroups()
{
Professions = new HashSet<Professions>();
}
public int ProfessionGroupID { get; set; }
public int ProfessionGroupTitle { get; set; }
public string ProfessionGroupDescription { get; set; }
public virtual ICollection<Professions> Professions { get; set; }
}
How can I display list of groups of professions in one columns and list of professions in another column
with links which leads to each profession Details View?
This is how output table in some View should look:
ProfessionGroups
Professions
Physiotherapy
Graduated physiotherapist Physiotherapist (bachelor degree) Physiotherapy technician
Geology
Geological technician Geology engineer
So far I've created ViewModel that suppose to hold needed data:
public class ProfGroupsVM
{
public int ProfessionGroupID { get; set; }
public string ProfessionGroupTitle { get; set; }
public List<int> ProfIDs { get; set; } = new List<int>();
public List<string> ProfTitles { get; set; } = new List<string>();
}
For each ProfessionGroupID it has to populate ProfIDs with list of professions belonging to that group and ProfTitles should hold professions titles.
So in Controller so far I have:
var profGroupsVM= new ProfGroupsVM();
var result = from p in _context.Professions
group p.ProfessionID by p.ProfessionGroupID into g
select new { ProfessionGroupID = g.Key, ProfessionIDs= g.ToList() };
I don't know how to assign this result back to my ViewModel nor how to display desired View.
It's better to change the ProfGroupsVM model to this:
public class ProfGroupsVM
{
public int ProfessionGroupID { get; set; }
public string ProfessionGroupTitle { get; set; }
public List<ProfInfo> Professions { get; set; }
public class ProfInfo
{
public int Id { get; set; }
public string Title { get; set; }
}
}
And to get information use this:
_dbContext.ProfessionGroups.Include(x => x.Professions)
.Select(x => new ProfGroupsVM()
{
ProfessionGroupID = x.ProfessionGroupID,
ProfessionGroupTitle = x.ProfessionGroupTitle,
Professions = x.Professions.Select(p => new ProfGroupsVM.ProfInfo
{
Id = p.ProfessionID,
Title = p.ProfessionTitle
}).ToList()
}).ToList();
And in you view try this to show information(I'm not good in UI :)):
#model List<ProfGroupsVM>
<div>
<ul>
#foreach (var group in Model)
{
<li>#group.ProfessionGroupTitle</li>
<ul>
#foreach (var profession in group.Professions)
{
<li>#profession.Title</li>
}
</ul>
}
</ul>
Or if you stick with your ViewModel for ul part you can put this:
<ul>
#foreach (var idsTitles in item.ProfIDs.Zip(#item.ProfTitles, Tuple.Create))
{
<li><a asp-action="Details" asp-controller="Profession" asp-route-id="#idsTitles.Item1">#idsTitles.Item2</a></li>
}
</ul>

Handling Nested Objects in Entity Framework

I am struggling a bit to wrap my head around Entity Framework and It's driving me crazy. I have an target object that I'd like to populate:
public class ApiInvitationModel
{
public int Id { get; set; }
public EventModel Event { get; set; }
public UserModel InvitationSentTo { get; set; }
public UserModel AttendingUser { get; set; }
}
The schemas of the above models are:
public class EventModel {
public int Id? { get; set; }
public string Name { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set }
public OrganizationModel HostingOrganization { get; set; }
public Venue Venue { get; set; }
public string Price { get; set; }
}
public class UserModel {
public int Id? { get; set; }
public string Name { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public List<OrganizationModel> Organizations { get; set; }
}
public class OrganizationModel {
public int Id? { get; set; }
public stirng Name { get; set; }
public string Address { get; set; }
public UserModel PrimaryContact { get; set; }
}
The above schemas are simplified for the purpose of the question and are the models we intend to return via API.
The problem is the origin schemas in the database is very different and I'm trying to map the database objects to these objects via Entity Framework 6.
My attempted solution was to try and nest the models via a query but that didn't work and I'm not sure where to go from here besides making numerous calls to the database.
public List<ApiInvitationModel> GetInvitations(int userId) {
using (var entities = new Entities()) {
return entities.EventInvitations
.Join(entities.Users, invitation => invitiation.userId, user => user.id, (invitation, user) => new {invitation, user})
.Join(entities.Events, model => model.invitation.eventId, ev => ev.id, (model, ev) => new {model.invitation, model.user, ev})
.Join(entities.organization, model => model.user.organizationId, organization => organization.id, (model, organization) => new ApiInvitationModel
{
Id = model.invitation.id,
Event = new EventModel {
Id = model.event.id,
Name = model.event.name,
StartDate = model.event.startDate,
EndDate = model.event.endDate,
HostingOrganization = new OrganizationModel {
Id = model.invitation.hostingId,
Name = model.event.venueName,
Address = model.event.address,
PrimaryContact = new UserModel {
Name = model.event.contactName,
PhoneNumber = model.event.contactNumber,
}
}
...
},
InvitedUser = {
}
}
).ToList();
}
}
As you can see above, there's quite a bit of nesting going on but this doesn't work in Entity Framework 6 as far as I am aware. I keep getting the following errors:
"The type 'Entities.Models.API.UserModel' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.",
Based on the above error, I assumed that each of the model initiatilizations would need to be the same (i.e. initializing the values as the same ApiInvitationModel in each join in the same order) but that produces the same error.
What would be the best approach to handling this, keepign in mind the source database doesn't have foreign keys implemented?

Select from self-referencing table and convert to viewmodel

How can i select all levels of a self-referencing table as a view model. if max level was 2 or 3 then i can do that by calling Select multiple times but i have 4-5 level menus and i think there should be a better solution for doing that and select all levels.
this is my viewmodel:
public class MenuViewModel
{
public MenuViewModel()
{
Childs = new HashSet<MenuViewModel>();
}
public int Id{ get; set; }
public string Title { get; set; }
public string Url { get; set; }
public ICollection<MenuViewModel> Childs { get; set; }
}
and this is my Menu class:
public class Menu
{
public Menu()
{
Childs = new HashSet<Menu>();
}
public int Id{ get; set; }
public string Title { get; set; }
public string Url { get; set; }
public string Description { get; se; }
public byte[] Icon { get; set; }
public int Order { get; set; }
public ICollection<Menu> Childs { get; set; }
}
var viewModel = _dataContext.Menus
.Select(x => new MenuViewModel
{
Id = x.Id,
Title = x.Title,
Child = ???
}
.ToList();
When you are using EF , you can do like following way:
public class BlogComment
{
public int Id { set; get; }
[MaxLength]
public string Body { set; get; }
public virtual BlogComment Reply { set; get; }
public int? ReplyId { get; set; }
public ICollection<BlogComment> Children { get; set; }
}
using (var ctx = new MyContext())
{
var list = ctx.BlogComments
//.where ...
.ToList() // fills the childs list too
.Where(x => x.Reply == null) // for TreeViewHelper
.ToList();
}
with this way you don't need to use recursive queries but As far as I know,when use view model for fetch data , the dynamic proxy of EF Is destroyed.
about above example:
just select one list of comments and with
.Where(x=>x.Reply==null).Tolist()
EF fill children property of Comments.
Reference
Assuming that Id property is unique you can do it in two passes:
Create viewmodel items without children, but with associated children ids. From that data create the Dictionary that will allow you to get any viewmodel by its id. Values in this dictionary will be the created viewmodels alongside their children ids.
For each viewmodel item get the associated view model items using the children ids.
Something like:
var tempModels = _dataContext
.Menus
.Select(menu => new
{
childrenIds = menu.Childs.Select(item => item.Id).ToArray(),
viewModel =
new MenuViewModel
{
Id = menu.Id,
Title = menu.Title
}
})
.ToDictionary(
keySelector: item => item.viewModel.Id);
var viewModels = tempModels
.Select(kv =>
{
var viewModel = kv.Value.viewModel;
viewModel.Childs = kv
.Value
.childrenIds
.Select(childId =>
tempModels[childId].viewModel)
.ToList();
return viewModel;
})
.ToList();
for depth problem you can use one int property like Depth in your Model then you can fetch data like this :
public class BlogComment
{
public int Id { set; get; }
[MaxLength]
public string Body { set; get; }
public int Depth{get;set}
public virtual BlogComment Reply { set; get; }
public int? ReplyId { get; set; }
public ICollection<BlogComment> Children { get; set; }
}
using (var ctx = new MyContext())
{
var list = ctx.BlogComments
.Where(a=>a.Depth<2)
.ToList() // fills the childs list too
.Where(x => x.Reply == null) // for TreeViewHelper
.ToList();
}
for using viewModel in this senario , I Test with AutoMapper,but when select data with viewModel , the dyamic proxy that EF generate is Destroyed .
Please Note this Issue

How do I update a record that has a child entity?

I am trying to update a record and its child at the same time. When I create the object from the database the child property is null (the property is a generic list).
I want to update the class and also update the child class without creating duplicated records in the system.
Here is how I generate the object:
var r = db.SupplierAs.Where(o => o.id == 1).First();
The SupplierA class has a property List. Using the above line of code this comes back null. I have been trying work out the code to initialize this property so I can update it but I am having no joy.
This is the original item I created:
db.Products.Add(new Product
{
name = "product test",
supplierA = new SupplierA
{
name = "supA",
price = 1.99m,
sku = "abc123",
otherCurrencies = new List<Currency>
{
new Currency
{
eur = 2.99m,
usd = 3.99m
}
}
},
});
db.SaveChanges();
I can update the supplier on its own easily like so:
var r = db.SupplierAs.Where(o => o.id == 1).First();
r.name = "Updated name";
db.SupplierAs.Attach(r);
db.Entry(r).State = EntityState.Modified;
db.SaveChanges();
But I cannot figure out how to generate the Currency object list as part of the SupplierAs object. Currencies doesnt seem to be in the db context.
Here are the class files:
public class Product
{
public int id { get; set; }
public string name { get; set; }
public virtual SupplierA supplierA { get; set; }
}
public class SupplierA
{
public int id { get; set; }
public string name { get; set; }
public string sku { get; set; }
public decimal price { get; set; }
public List<Currency> Currencies { get; set; }
}
public class Currency
{
public int id { get; set; }
public decimal eur { get; set; }
public decimal usd { get; set; }
}
The idea of products, suppliers and currencies doesn't make the greatest sense I know, I have extracted logic from my app in example, hopefully it makes enough sense what I am trying to achieve.

Accessing dynamically added HTML controls in ASP.net MVC

I have a problem which I'm unable to solve so any help would be appreciated. I have a view in which I'm dynamically adding textboxes (depending of a value chosen in dropdownlist).
Basically, I'm entering data for the product which depending of the category it belongs to has specific attributes added to it. For example, if the product is soft dring it could have following attributes: type of packaging, flavor, volume, etc. while some other product like cell phone may have attributes like: weight, RAM, CPU clock, CPU type, etc.
This is how the database looks like:
Dynamically creating controls isn't a problem and it is done with this code:
<script type="text/javascript">
$(document).ready(function () {
$("#ProductCategoryId").change(function () {
if ($("#ProductCategoryId").val() != "") {
var options = {};
options.url = "http://localhost:59649/Product/GetProductCategoryAttributes";
options.type = "POST";
options.data = JSON.stringify({ id: $("#ProductCategoryId").val() });
options.dataType = "json";
options.contentType = "application/json";
options.success = function (productCategoryAttributes) {
$("#atributtes").empty();
for (var i = 0; i < productCategoryAttributes.length; i++) {
$("#atributi").append("<div class='editor-label'><label>" + productCategoryAttributes[i].Name + "</label></div>")
.append("<div class='editor-field'><input class='text-box single-line' id='" + productCategoryAttributes[i].Name + "' name='" + productCategoryAttributes[i].Name + "' type='text'>");
}
};
options.error = function () { alert("Error retrieving data!"); };
$.ajax(options);
}
else {
$("#atributtes").empty();
}
});
});
</script>
Method in controller that retrieves ProductAttributeCategory names depending of ProductCategoryId selected:
public JsonResult GetProductCategoryAttributes(int id)
{
var productCategoryAttributes = db.ProductCategoryAttribute
.Where(p => p.ProductCategoryId == id)
.Select(p => new { Name = p.Name, p.DisplayOrder })
.OrderBy(p => p.DisplayOrder)
.ToList();
return Json(productCategoryAttributes, JsonRequestBehavior.AllowGet);
}
Controller code for POST:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Product product)
{
if (ModelState.IsValid)
{
db.Product.Add(product);
db.SaveChanges();
var productCategoryAttributes = db.ProductCategoryAttribute
.Where(p => p.ProductCategoryId == product.ProductCategoryId)
.OrderBy(p => p.DisplayOrder);
foreach (ProductCategoryAttribute productCategoryAttribute in productCategoryAttributes)
{
//Find HTML element that matches productCategoryAttribute.Name
ProductProductCategoryAttribute productCategoryAttributeValue = new ProductProductCategoryAttribute();
productCategoryAttributeValue.ProductId = product.ProductId;
//productCategoryAttributeValue.ProductCategoryAttributeId = Find HTML element that matches ProductCategoryAttributeID and pass its id here
//productCategoryAttributeValue.Value = Find HTML element that matches ProductCategoryAttributeID and pass its value here
db.ProductProductCategoryAttribute.Add(productCategoryAttributeValue);
db.SaveChanges();
}
return RedirectToAction("Index");
}
ViewBag.LanguageId = new SelectList(db.Language, "LanguageId", "Name", product.LanguageId);
ViewBag.ProductCategoryId = new SelectList(db.ProductCategory, "ProductCategoryId", "Name", product.ProductCategoryId);
return View(product);
}
Product model:
public partial class Product
{
public Product()
{
this.ProductPhoto = new HashSet<ProductPhoto>();
this.ProductProductCategoryAttribute = new HashSet<ProductProductCategoryAttribute>();
}
public int ProductId { get; set; }
public int LanguageId { get; set; }
public int ProductCategoryId { get; set; }
public string Name { get; set; }
public string EAN { get; set; }
public string Description { get; set; }
public virtual Language Language { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
public virtual ICollection<ProductPhoto> ProductPhoto { get; set; }
public virtual ICollection<ProductProductCategoryAttribute> ProductProductCategoryAttribute { get; set; }
}
ProductCategory model:
public partial class ProductCategory
{
public ProductCategory()
{
this.Product = new HashSet<Product>();
this.ProductCategoryAttribute = new HashSet<ProductCategoryAttribute>();
}
public int ProductCategoryId { get; set; }
public int LanguageId { get; set; }
public string Name { get; set; }
public string PhotoLocation { get; set; }
public int DisplayOrder { get; set; }
public bool Active { get; set; }
public virtual Language Language { get; set; }
public virtual ICollection<Product> Product { get; set; }
public virtual ICollection<ProductCategoryAttribute> ProductCategoryAttribute { get; set; }
}
ProductCategoryAttribute model:
public partial class ProductCategoryAttribute
{
public ProductCategoryAttribute()
{
this.ProductProductCategoryAttribute = new HashSet<ProductProductCategoryAttribute>();
}
public int ProductCategoryAttributeId { get; set; }
public int ProductCategoryId { get; set; }
public string Name { get; set; }
public string MetaName { get; set; }
public string SymbolLocation { get; set; }
public int DisplayOrder { get; set; }
public bool Active { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
public virtual ICollection<ProductProductCategoryAttribute> ProductProductCategoryAttribute { get; set; }
}
What I can't figure out is how to get the values from those dynamically created textboxes. Pseudocode (inside the controller) would be something like this:
Get the ProductCategoryId of the product
List all the attributes belonging to the selected product category
For each attribute find the appropriate textbox inside the view and get the value entered
Save the value to the database
I'm fairly new to the MVC so my approach may be wrong. Feel free to correct me.
It's very hard to read your code so here is a simplified version that should help you. Suppose you have these two models:
public class ProductCategory
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
}
public class Product
{
public Product()
{
Categories = new List<ProductCategory>();
}
public int ProductId {get;set;}
public string ProductName { get; set; }
public IEnumerable<ProductCategory> Categories { get; set; }
}
If these where your models then then the name attribute of your dynamically added textbox should be:
<input type="textbox" name="Categories[i].CategoryName" />
You can safely ignore the id attribute since name attribute is enough for proper model mapping/binding. Whatever value you enter in the textbox should map into an instance of a ProductCategory's CategoryName in the list of Categories attached to the Product model...
Thank you both of you. I've found a quick and, as it seems, dirty way of accomplishing this.
Basically, you can access any HTML control by using:
var value = Request["controlName"];
That is how I was able to get values from the form. I don't know if this is the right way but it sure worked for me. Any suggestion for improving this is more than welcome.

Categories