Eager Loading - MVC - asp.net-mvc

I have 2 models: (1) FamiliaMáquina (PK: FamiliaId) and (2) TipoMáquina (PK: TipoMáquinaId).
The relation between these is: TipoMáquina can have only 1 FamiliaId associated, while FamiliaMáquina can have many TipoMáquinaId associated (one to many relationship).
FamiliaMáquina definition:
public class FamiliaMáquina
{
[Key, Required]
public int FamiliaId { get; set; }
public string DescripciónFamilia { get; set; }
public virtual ICollection<TipoMáquina> TipoMáquina { get; set; }
}
TipoMáquina definition:
public class TipoMáquina
{
[Key, Required]
public int TipoMáquinaId { get; set; }
public string DescripciónTipoMáq { get; set; }
public int FamiliaId { get; set; }
public virtual FamiliaMáquina FamiliaMáquina { get; set; }
}
The objetive: Show, programing the FamiliaMáquina controller, a list of FamiliaId (from model 1) and DescripciónTipoMáq (from model 2) associated. For this, the tables have been populated with sample data.
On the FamiliaMaqController (where the association takes places - eager loading):
public class FamiliaMaqController : Controller
{
private ApplicationDbContext _context;
public FamiliaMaqController()
{
_context = new ApplicationDbContext();
}
protected override void Dispose(bool disposing)
{_context.Dispose();}
public async Task<ActionResult> NuevaMQ()
{
var fammáquinas = _context.FamiliaMáquinas.Include(c => c.TipoMáquina).AsNoTracking();
return View(await fammáquinas.ToListAsync());
}
}
This is supossed to show on NuevaMQ.cshtml. NuevaMQ code is:
#model IEnumerable<Plataforma.Models.FamiliaMáquina>
#{
ViewBag.Title = "NuevaMQ";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>NuevaMQ</h2>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Familia de Máquina</th>
<th>Tipo de Máquina</th>
</tr>
</thead>
<tbody>
#foreach (var fammáquinas in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => fammáquinas.FamiliaId)</td>
<td>#Html.DisplayFor(modelItem => fammáquinas.TipoMáquina.DescripciónTipoMáq)</td>
</tr>
}
</tbody>
</table>
I'm getting an error on this line:
#Html.DisplayFor(modelItem => fammáquinas.TipoMáquina.DescripciónTipoMáq
Supposedly, FamiliaID and DescripciónTipoMáq should be listed, however, I get the following warning:
"ICollection doesn't have a definition for 'DescripciónTipoMáq'."
¿Why isn't eager loading working? I've been struggling with this for a while now. It's like this line of code:
var fammáquinas = _context.FamiliaMáquinas.Include(c => c.TipoMáquina).AsNoTracking();
Is not associating the tables but I'm not getting an error there.
What I'm getting as a result in the website is:
FamiliaId: 1
DescripciónTipoMáq: System.Data.Entity.DynamicProxies.FamiliaMáquina_EB64915DB33A48C74F57D392EFE55D5AEDC5BF74BE67B819F4737EEB09B6F436
This repeats as many times as items related per FamiliaId. It's like the program is iterating as it should but getting that reference everytime instead of the field value.

You've the error in that line
#Html.DisplayFor(modelItem => fammáquinas.TipoMáquina.DescripciónTipoMáq)
Because based on the code of FamiliaMáquina class you're using TipoMáquina which is a collection ICollection<TipoMáquina>. And ICollection<TipoMáquina> likee all ICollection<T> doesn't define DescripciónTipoMáq property that is what the error said:
ICollection doesn't have a definition for 'DescripciónTipoMáq'.
So if you wan't to get the description you need to iterate through each TipoMáquina item and display DescripciónTipoMáq property
Side Note: Avoid using that kind of naming things with accent because you or another developper can lost a lot of time to know why the code doesn't compile and will find that after several minutes that he or she mispelled the name of the varaible.

It worked!
I've changed the controller in this way:
public async Task<ActionResult> NuevaMQ()
{
var fammáquinas = _context.TipoMáquinas.Include(c => c.FamiliaMáquina).AsNoTracking();
return View(await fammáquinas.ToListAsync());
}
Then, I went to the view to change it accordingly so I could invok the data from FamiliaMáquina and brind the FamiliaId:
<tbody>
#foreach (var fammáquinas in Model)
{
<tr>
<td>#Html.DisplayFor(modelItem => fammáquinas.FamiliaId)</td>
<td>##Html.DisplayFor(modelItem => fammáquinas.DescripciónTipoMáq)</td>
</tr>
}
</tbody>
also, changed the model of reference at top of the cshtml file:
#model IEnumerable<Plataforma.Models.TipoMáquina>
And as result, the table showed both atributes invoked from diferent tables :) Thanks for the help!

Related

ASP.NET MVC : passing to view nested objects

I have the following model :
public class Business
{
[Key]
public int BusinessId { get; set; }
public string Name {get;set;}
....
.....
....
[Display(Name = "Owner")]
public int OwnerId { get; set; }
[ForeignKey("OwnerId")]
public ApplicationUser Owner { get; set; }
}
In my controller I have the following function :
public async Task<IActionResult> Index()
{
return View(await _context.Business.ToListAsync());
}
and my view :
#model IEnumerable<ServiceProviderApp.Models.Business>
#{
ViewData["Title"] = "Business Engine";
}
.....
......
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
......
<td>
#Html.DisplayFor(modelItem => item.Owner.Name)
</td>
<td>
As you can understand, the Owner inside the Business is an Object. Therefore, when I try to access it in the view (item.Owner.Name) I don't get anything (null). I can change the query on the controller to return a record that is joined but then the model on the view won't be business any more.
I searched online but didn't find a good and clear explanation. Not sure why the EF doesn't bind the nested objects automatically.
One option that I saw on YouTube but I think that it sucks to do:
Create a new model with all the columns after the join
In my controller, run the join and pass it to the view
In the view accept the new model as parameter
Link - https://www.youtube.com/watch?v=v1B3R-kb9CU
Solution : You need to use the include func in the controller :
public async Task<IActionResult> Index()
{
var business = _context.Business.Include("Owner");
return View(await business.ToListAsync());
}
Thanks to DanielShabi for the help.

Using Include in Entity Framework Core

Question: How and where the extension method "Include", used in the Index() action method, in the following PostController used in the Inxex.cshtml view shown below? As I understand _context.Posts.Include(p => p.Blog) means include all posts that relate to blogs table. But I don't see use of blog class or blogId property in the Index.cshtml view below?
Background: In an ASP.NET MVC Core - Code First project I'm following this ASP.NET official site tutorial where they have following Model classes for Blog (parent) and Post (child). I then created a controller (shown below) using MVC Controller with Views, using Entity Framework wizard where I selected Post model in the Model dialogbox.:
Model:
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
namespace EFGetStarted.AspNetCore.NewDb.Models
{
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
}
PostController:
public class PostsController : Controller
{
private readonly BloggingContext _context;
public PostsController(BloggingContext context)
{
_context = context;
}
// GET: Posts
public async Task<IActionResult> Index()
{
var bloggingContext = _context.Posts.Include(p => p.Blog);
return View(await bloggingContext.ToListAsync());
}
}
Index.cshtml view for Index() action in PostController:
#model IEnumerable<ASP_Core_Blogs.Models.Post>
#{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Content)
</th>
<th>
#Html.DisplayNameFor(model => model.Title)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Content)
</td>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
<a asp-action="Edit" asp-route-id="#item.PostId">Edit</a> |
<a asp-action="Details" asp-route-id="#item.PostId">Details</a> |
<a asp-action="Delete" asp-route-id="#item.PostId">Delete</a>
</td>
</tr>
}
</tbody>
</table>
Since you're not accessing the Blog property, it's better not to use Include(p => p.Blog). This will add an extra join that isn't required.
However, if you'll reference it in each table row, then it's preferred to include it to avoid lazy loading issues.

ASP MVC Nested Models

I need a view that displays rows of header-detail info. For a trivial case I set up a Course-Student tiny database with 4 simple tables. The Courses and Students tables are linked with a Section table which also has a student grade in it. Also, the Student table is owned by a Student Type Table. There are helper classes to hold the list of "header" data for the course and a "detail" list for each student who takes that class.
namespace SchoolA.Models
{
public class CourseInfo2
{
public int CourseID { get; set; }
public int CourseNumber { get; set; }
public string CourseName { get; set; }
public IEnumerable<CourseSectionList> CourseStudentList { get; set; }
}
}
namespace SchoolA.Models
{
public class CourseSectionList
{
public string Student { get; set; }
public string Type { get; set; }
public string Grade { get; set; }
}
}
The controller is:
public ActionResult Courselisting()
{
List<CourseInfo2> courseInfo2 = new List<CourseInfo2>();
List<CourseSectionList> courseSectionList = new List<CourseSectionList>();
var Cquery = from c in db.Courses select c;
foreach (var item in Cquery)
{
Course course = db.Courses.Find(item.CourseID); // first find the selected course
// get the sections
foreach (var s in query) // go through each section
{
// get the section data
courseSectionList.Add(new CourseSectionList
{
Student = student.StudentName,
Type = studentType.StudentTypeName,
Grade = s.SectionStudentGrade
});
} // end of section loop
courseInfo2.Add(new CourseInfo2
{
CourseID = course.CourseID,
CourseNumber = course.CourseNumber,
CourseName = course.CourseName,
CourseStudentList = courseSectionList
});
} // end of Course loop
return View(courseInfo2); // Course List and Section list for each course
}
The View is:
#model SchoolA.Models.CourseInfo2
#* doesn't work either: model List<SchoolA.Models.CourseInfo2>*#
<div>
<table>
<tr><th>ID</th> <th>Number</th> <th>Course</th></tr>
#foreach (var CourseInfo2 in Model)
{
<tr>
<td>
#CourseInfo2.CourseID
</td>
<td>
#CourseInfo2.CourseNumber
</td>
<td>
#CourseInfo2.CourseName
</td>
<td>
<table class="table">
<tr><th>Student</th> <th>Type</th><th>Grade</th></tr>
#foreach (var s in Model.CourseStudentList)
{
<tr>
<td>
#s.Student
</td>
<td>
#s.Type
</td>
<td>
#s.Grade
</td>
</tr>
}
</table>
</td>
</tr>
}
</table>
</div>
The problem is that I get a variety of errors as I try to pass these two models to the view. In the code as shown above I get this error:
CS1579: foreach statement cannot operate on variables of type 'SchoolA.Models.CourseInfo2' because 'SchoolA.Models.CourseInfo2' does not contain a public definition for 'GetEnumerator'
I've tried a number of variations for passing the models but always run into one error other the other that prevents both models from working in the view. I should note that I tested each part of the view independently and the controller code works fine to deliver the correct data to the view. However, I can't combine the two.
The problem seems to be the way I create instances of the models an how they are passed to the view. What am I doing wrong?
You're passing your view a List<SchoolA.Models.CourseInfo2>, but the view expects a single SchoolA.Models.CourseInfo2.
Change your #model declaration to IEnumerable<SchoolA.Models.CourseInfo2>.
Then change your foreach loops.
Change the first loop to foreach (var courseInfo in Model)
Change the inner loop to foreach (var s in courseInfo.CourseStudentList)
You are passing a model which is list of object, while in view you have single object.
Change your view model bind as below:
#model List<SchoolA.Models.CourseInfo2>
Problem: I have to edit Level2Name (by UI it's a textbox inside the child grid).
I'm able to edit Level1name (parent grid text field) and able to get value in my controller.
Question: How do I able to edit the nested textbox.
What I've tried.
Code:
Model
public class Level0
{
public Level0()
{
Level1= new List<Level1>();
}
public int? ID{ get; set; }
public int? NameText{ get; set; }
public List<Level1> lev1{ get; set; }
}
public class Level1
{
public Level1()
{
Level2= new List<Level2>();
}
public int Lev1ID {get;set;}
public string Level1name{ get; set; }
public List<Level2> level2{ get; set; }
}
public class Level2
{
public string Level2_ID { get; set; }
public string Level2Name{ get; set; }
}
UI Code
#for (int i = 0; i < Model.Level1.Count; i++)
{
#Html.HiddenFor(modelItem => Model.Floors[i].Lev1ID )
#for (int j = 0; j < Model.Level1[i].Level2.Count; j++)
{
#Html.HiddenFor(m => m.Level1[i].Level2[j].OP)
#Html.TextBoxFor(m => m.Level1[i].Level2[j].Level2Name, new { #class = "form-control" })
}
}

Using 2 models in one view in ASP.NET MVC 5

I am new to ASP.NET MVC and I am kindly asking you to help me with this problem:
I have 2 models - Delegation:
public int idDelegation { get; set; }
public string Delegation_Name { get; set; }
public string Employee_Name { get; set; }
and Project:
public int idProject { get; set; }
public string Project_System_Name { get; set; }
public string Project_System_ID { get; set; }
I want to display in a view a list of current delegations and projects, with the Edit/Details/Delete options for each one.
I created a ViewModel DelegationProject:
public class DelegationProject
{
public IEnumerable<Delegation> delegations { get; set; }
public IEnumerable<Project> projects { get; set; }
}
In my Index view I have:
#model IEnumerable<....ViewModel.DelegationProject>
<table class="table">
<tr>
<th>#Html.DisplayNameFor(model => model.delegations.Employee_Name</th>
<th>#Html.DisplayNameFor(model => model.delegations.Delegation_Name</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(itemModel => item.delegations.Employee_Name)</td>
<td>#Html.DisplayFor(itemModel => item.delegations.Delegation_Name</td>
<td>
#Html.ActionLink("Edit", "Edit", new { x => x.idDelegation }) |
#Html.ActionLink("Details", "Details", new { x => x.idDelegation }) |
#Html.ActionLink("Delete", "Delete", new { x => x.idDelegation })
</td>
</tr>
}
</table>
When I run the application, I get these error messages:
Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.
Compiler Error Message: CS1061: 'System.Collections.Generic.IEnumerable' does not contain a definition for 'Employee_Name' and no extension method 'Employee_Name' accepting a first argument of type 'System.Collections.Generic.IEnumerable' could be found (are you missing a using directive or an assembly reference?)
I tried different things (like List<> or Tuple) from stackoverflow topics and other sites posts, but I wasn't able to use to models in the same view and iterate through them.
I managed to find a solution for this problem.
The ViewModel DelegationProject remained the same, in my controller for Index action I have:
public ActionResult Index()
{
DelegationProject viewModel = new DelegationProject();
viewModel.delegations = GetDelegations();
viewModel.projects = GetProjects();
return View(viewModel);
}
And I created two methods in the respective controller - GetDelegations() and GetProjects():
public List<Delegation> GetDelegations()
{
var deleg = db.Delegation.Include(d => d.Project);
var list = new List<Delegation>(deleg).ToList();
return list;
}
public List<Project> GetProjects()
{
var projects = db.Project.Include(p => p.Country);
var list = new List<Project>(projects);
return list;
}
My View looks like this:
#model .......ViewModel.DelegationProject
#foreach (var item in Model.delegations)
{
<tr>
<td>
#Html.DisplayFor(itemModel => item.Employee_Name)
</td>
<td>
#Html.DisplayFor(itemModel => item.Delegation_Name)
</td>
</tr>
}
Hope this could be of any help to somebody.

How to use two IENumerable models in one view

I am trying to use two models in one view, but from what I understand my program just don't see any objects within models.
Here is my code.
Models:
public class Album
{
[Key]
public int ThreadId { get; set; }
public int GenreId { get; set; }
public string Title { get; set; }
public string ThreadByUser { get; set; }
public string ThreadCreationDate { get; set; }
public string ThreadContent { get; set; }
public Genre Genre { get; set; }
public List<Posts> Posty { get; set; }
}
public class Posts
{
[Key]
public int PostId { get; set; }
public int ThreadId { get; set; }
public string PostTitle { get; set; }
public string PostContent { get; set; }
public string PostDate { get; set; }
public string PosterName { get; set; }
public Album Album { get; set; }
}
public class ModelMix
{
public IEnumerable<Posts> PostsObject { get; set; }
public IEnumerable<Album> ThreadsObject { get; set; }
}
Index controller code:
public ActionResult Index(int id)
{
ViewBag.ThreadId = id;
var posts = db.Posts.Include(p => p.Album).ToList();
var albums = db.Albums.Include(a => a.Genre).ToList();
var mixmodel = new ModelMix
{
PostsObject = posts,
ThreadsObject = albums
};
return View(mixmodel);
}
View code:
#model MvcMusicStore.Models.ModelMix
<h2>Index</h2>
#Html.DisplayNameFor(model => model.PostsObject.PostContent)
And when I try to execute my program I am getting this error:
CS1061: The "
System.Collections.Generic.IEnumerable 'does not contain a definition
of" PostContent "not found method of expanding" PostContent ", which
takes a first argument of type' System.Collections.Generic.IEnumerable
"
How I can make it work as intended? There are a lot of questions like mine on the internet but I couldn't find any matching my case.
Looping over models can be a little confusing to begin with in MVC, only because the templated helpers (i.e. Html.DisplayFor and Html.EditorFor) can be provided templates which the helper will automatically invoke for every element in a collection. That means if you're new to MVC, and you don't realise a DisplayTemplate or an EditorTemplate has not been provided for the collection already, it looks as though a simple:
#Html.DisplayFor(m => m.SomePropertyThatHoldsACollection)
is all you need. So if you've seen something like that already, that might be why you made the assumption it would work. However, let's assume for a moment that a template has not been provided. You have two options.
Firstly, and most simply, would be to use foreach over the collection:
#foreach (var post in Model.PostsObject)
{
#Html.DisplayFor(m => post.PostTitle)
// display other properties
}
You could also use a for loop, but with IEnumerable<T>, there is no indexer, so this won't work:
#for (int i = 0; i < Model.PostsObject.Count(); i++)
{
// This generates a compile-time error because
// the index post[i] does not exist.
// This syntax would work for a List<T> though.
#Html.DisplayFor(m => post[i].PostTitle)
// display other properties
}
If you did want to use the for loop still, you can use it like so:
#for (int i = 0; i < Model.PostsObject.Count(); i++)
{
// This works correctly
#Html.DisplayFor(m => post.ElementAt(i).PostTitle)
// display other properties
}
So use whichever you prefer. However, at some point it would be a good idea to look into providing templates for your types. (Note: Although this article was written for MVC 2, the advice still applies.) They allow you to remove looping logic from your views, keeping them cleaner. When combined with Html.DisplayFor, or Html.EditorFor, they will also generate correct element naming for model binding (which is great). They also allow you to reuse presentation for a type.
One final comment I'd make is that the naming of your properties is a little verbose:
public class ModelMix
{
public IEnumerable<Posts> PostsObject { get; set; }
public IEnumerable<Album> ThreadsObject { get; set; }
}
We already know they're objects, so there's no need to add that on the end. This is more readable:
public class ModelMix
{
public IEnumerable<Posts> Posts { get; set; }
public IEnumerable<Album> Threads { get; set; }
}
You need to iterate them like this:
#model MvcMusicStore.Models.ModelMix
<h2>Index</h2>
#for(var i=0; i<model.PostsObject.Count(); i++)
{
#Html.DisplayNameFor(model => model.PostsObject[i].PostContent)
}
And also it's better to save IList instead of IEnumerable, as it will have Count property, instead of using Count() method
Typically you'll need to iterate through each item if you're passing in any type of IEnumerable<>. Since you built a semi-complex model, you'll want to display foreach item in each list. Here is an example based from the ASP.NET MVC tutorials that I think would help you a bit:
#model IEnumerable<ContosoUniversity.Models.Course>
#{
ViewBag.Title = "Courses";
}
<h2>Courses</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.CourseID)
</th>
<th>
#Html.DisplayNameFor(model => model.Title)
</th>
<th>
#Html.DisplayNameFor(model => model.Credits)
</th>
<th>
Department
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.CourseID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
#Html.DisplayFor(modelItem => item.Credits)
</td>
<td>
#Html.DisplayFor(modelItem => item.Department.Name)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.CourseID }) |
#Html.ActionLink("Details", "Details", new { id=item.CourseID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.CourseID })
</td>
</tr>
}
</table>
Generally most people use an ICollection for their lists, where an example might be in an object:
public virtual ICollection<Post> Posts { get; set; }
Source:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application
I would recommend starting at the beginning as it'll help you understand why you need to do this:
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc

Resources