Displaying information from another model on index - c#

Noob warning - learning MVC for an academic project :-)
I have two models and I'm trying to show ProjectName (from first model) in my index view of Actors (from my second model). Everything loads up OK and I'm able to display projectId from my Actor class, but it's not picking up a value for #Html.DisplayFor(modelItem => item.project.ProjectName) in my index table.
However, it is picking up the correct [DisplayName("Project Name") in the table, which makes me think it is making some connection - just can't understand why it's not getting the value for it, I just have an empty column with a correctly labelled header!
I had planned on using a ViewModel to achieve this, but in the Wrox Professional ASP.NET MVC 4 book, the authors do what appears to be the same as what I am attempting in showing the Genre & Artist names (not ID's) in their Album view (MVC Music Store project - http://www.asp.net/mvc/overview/older-versions/mvc-music-store/mvc-music-store-part-4).
Help much appreciated!
public class Project
{
public int ID {get; set;}
[DisplayName ("Project Name")]
public string ProjectName { get; set; }
[DisplayName("Client")]
public string ClientID { get; set; }
public string Description { get; set; }
[DisplayName("Use Cases")]
public virtual ICollection <UseCase> UseCases { get; set; }
}
...
public class Actor
{
public int ID { get; set; }
public int projectID { get; set; }
public Project project { get; set; }
public string Title { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
}
Here is my controller code for the Index action (note, I'm passing the projectId in the URL and this is what I'm using as the parameter...
// GET: Actors
public ActionResult Index(int? id)
{
ViewBag.projectId = id;
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
else
{
return View(db.Actors.Where(x => x.projectID == id).ToList());
}
}
And here is the view code....
#model IEnumerable<JustSpecIt.Models.Actor>
#{
ViewBag.Title = "Actors";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Actors</h2>
<p>
#Html.ActionLink("Create New", "Create", new { id = ViewBag.projectId })
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.project.ProjectName)
</th>
<th>
#Html.DisplayNameFor(model => model.projectID)
</th>
<th>
#Html.DisplayNameFor(model => model.Title)
</th>
<th>
#Html.DisplayNameFor(model => model.Description)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.project.ProjectName)
</td>
<td>
#Html.DisplayFor(modelItem => item.projectID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
#Html.ActionLink("Details", "Details", new { id=item.ID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
</table>
#Html.ActionLink("<< Back", "ShowSteps", "Projects", new { id = ViewBag.projectId }, null)

Try to add virtual keyword to project property in Actor class to allow lazy loading of the related Project
public class Actor
{
public int ID { get; set; }
public int projectID { get; set; }
public virtual Project project { get; set; }
public string Title { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
}
See here for more info regarding lazy loading related entities: http://msdn.microsoft.com/en-us/data/jj574232#lazy

public class Project
{
public int ID {get; set;}
[DisplayName ("Project Name")]
public string ProjectName { get; set; }
[DisplayName("Client")]
public string ClientID { get; set; }
public string Description { get; set; }
[DisplayName("Use Cases")]
public virtual ICollection <UseCase> UseCases { get; set; }
}
public class Actor
{
public int ID { get; set; }
public int projectID { get; set; }
public virtual Project project { get; set; }
public string Title { get; set; }
[DataType(DataType.MultilineText)]
public string Description { get; set; }
}
// GET: Actors
public ActionResult Index(int? id)
{
ViewBag.projectId = id;
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
using (var context = new DbContext()) // initiate a connection to the DB
{
var actors = context.Actors.Where(x => x.projectID == id);
// tell it to include the projects
actors.Include("project"); // the name of the property
// now retrieve list from the DB
actors.ToList();
// return the actors to the view
return View(actors);
}
}
So the DbContext is what ever you context is called. We wrap it in a using to let make sure we manage the connection properly.
The virtual keyword in front of Project project {get;set;} is to enable Lazy loading, which can be bad in some cases (Causes SELECT N + 1 problems). To make sure we include all the projects in our list of actors we use the Include function and pass the name of the property through.
To bypass issues with SELECT N + 1, we call the ToList() method before we pass the values to the view. If we don't then the view will execute the get actors SQL and for each actor it will execute a call to get the project. So if we have 10 actors, we will make 11 calls. Which isn't good. ToList() makes sure that we execute 1 call, that provides us with all the values we will need in the view.
Then we execute the DB call with ToList(). And pass the values back to the view.

Related

Configure many-to-many relationship in ASP.NET Core MVC and Entity Framework against

I am trying to configure the relationship many-to-many between two tables, Employee and Project.
One Employee can participate in many projects, and one project can have many Employees working on it. So I created two model classes Employee and Project, and I added the table Employee_Project.
These are my three model classes:
namespace WebApp2.Models
{
public class Employee
{
[Key]
public int Emp_Id { get; set; }
public string Emp_Name { get; set; }
public string Emp_Email { get; set; }
public string Emp_Mobile { get; set; }
public virtual ICollection<Employee_Project> Employee_Projects { get; set; }
}
public class Project
{
[Key]
public int Proj_Id { get; set; }
public string Proj_Name { get; set; }
public string Project_Details { get; set; }
public virtual ICollection<Employee_Project> Employee_Projects { get; set; }
}
public class Employee_Project
{
[Key]
[Column(Order =1)]
public int Emp_Id { get; set; }
[Key]
[Column(Order = 2)]
public int Proj_Id { get; set; }
public virtual Employee Employee { get; set; }
public virtual Project Project { get; set; }
}
}
I then added this DbContext class:
namespace WebApp2.Data
{
public class MyDbContext:DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> option):base(option)
{
}
public DbSet<Employee> Employees { get; set; }
public DbSet<Project> Projects { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee_Project>().HasKey(pt => new { pt.Proj_Id, pt.Emp_Id });
modelBuilder.Entity<Employee_Project>()
.HasOne(pt => pt.Employee)
.WithMany(pt => pt.Employee_Projects)
.HasForeignKey(p => p.Emp_Id);
modelBuilder.Entity<Employee_Project>()
.HasOne(pt => pt.Project)
.WithMany(pt => pt.Employee_Projects)
.HasForeignKey(p => p.Proj_Id);
}
public DbSet<Employee_Project> Employee_Projects { get; set; }
}
}
I created after that the three controllers
public class ProjectController : Controller
{
private readonly MyDbContext _context;
public ProjectController(MyDbContext context)
{
_context = context;
}
public IActionResult Index()
{
return View(_context.projects.ToList());
}
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create(Project project)
{
_context.projects.Add(project);
_context.SaveChanges();
return RedirectToAction("Index");
}
}
public class EmployeeController : Controller
{
private readonly MyDbContext _context;
public EmployeeController(MyDbContext context)
{
_context = context;
}
public IActionResult Index()
{
return View(_context.Employees.ToList());
}
public IActionResult Create()
{
return View();
}
[HttpPost]
public IActionResult Create(Employee employee)
{
_context.Employees.Add(employee);
_context.SaveChanges();
return RedirectToAction("Index");
}
}
public class Emp_ProjController : Controller
{
private readonly MyDbContext _DbContext;
public Emp_ProjController(MyDbContext DbContext)
{
_DbContext = DbContext;
}
public IActionResult Index()
{
return View(_DbContext.Employee_Projects.ToList());
}
public IActionResult Create()
{
ViewBag.emp=_DbContext.Employees.ToList();
ViewBag.pro=_DbContext.projects.ToList();
return View();
}
[HttpPost]
public IActionResult Create(int empid, int [] projIds)
{
foreach (var item in projIds)
{
Employee_Project emp = new Employee_Project();
emp.Emp_Id = empid;
emp.Proj_Id = item;
_DbContext.Employee_Projects.Add(emp);
_DbContext.SaveChanges();
}
return RedirectToAction("Index");
}
}
After that foreach Controllers I made the view for the method Index and Create
Emp_Proj
//view Index
#model IEnumerable<WebApp2.Models.Employee_Project>
#{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Employee.Emp_Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Project.Proj_Name)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Employee.Emp_Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Project.Proj_Name)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ }) |
#Html.ActionLink("Delete", "Delete", new { /* id=item.PrimaryKey */ })
</td>
</tr>
}
</tbody>
</table>
//view Create
<h2>Create</h2>
<form method="post">
<div>
<label>Employee Name</label>
#Html.DropDownList("empid", new SelectList(ViewBag.emp, "Emp_Id","Emp_Email"),"Select Employee")
</div>
<div>
<label>Select Project</label>
#* #Html.DropDownList("proid", new SelectList(ViewBag.pro, "Proj_Id","Proj_Name"),"Select Project")*#
<ul>
#foreach(var item in ViewBag.pro )
{
<li>
<input type="checkbox" name="projIds" value="#item.Proj_Id">#item.Proj_Name
</li>
}
</ul>
<input type="submit" value="SaveData"/>
</div>
</form>
I don't have problem in the Employee and the project, I found the problem when I want to create a Emp_Proj element
enter image description here
It always gives me an error like that:
SqlException: Violation of PRIMARY KEY constraint 'PK_Employee_Projects'. Cannot insert duplicate key into object 'dbo.Employee_Projects'. Duplicate key value: (1, 1).
The instruction has been terminated.
enter image description here
Can someone please help me to find the problem? Thanks in advance.
I try to find the problem, and I appreciate some assistance.
The error message has show the reason why cause this exception: The database already contains this record, you can't insert duplicate data. You just need to check if it already exists in the database before inserting data, Please refer to this simple demo.
[HttpPost]
public IActionResult Create(int empid, int[] projIds)
{
foreach (var item in projIds)
{
//check if the database already has this record
var empdb = _DbContext.Employee_Projects.Where(x => x.Emp_Id == empid && x.Proj_Id == item).FirstOrDefault();
if (empdb==null)
{
Employee_Project emp = new Employee_Project();
emp.Emp_Id = empid;
emp.Proj_Id = item;
_DbContext.Employee_Projects.Add(emp);
}
}
_DbContext.SaveChanges();
return RedirectToAction("Index");
}
First off, try to remove "Employee_Project" entity entirely, and see if EF Core can auto-create it. EF Core should be able to auto-generate the association table as long as the two navigational properties are set on the two Entities. Note that navigational properties of the two entities are supposed to reference each other. like so;
in Employee;
public virtual ICollection<Project> Projects { get; set; }
and in Project;
public virtual ICollection<Employee> Employees { get; set; }
In case that doesn't work, modify your Employee_Project class like this;
public class Employee_Project
{
[Key]
[Column(Order =1)]
public int EmployeeId { get; set; }
[Key]
[Column(Order = 2)]
public int ProjectId { get; set; }
public virtual Employee Employee { get; set; }
public virtual Project Project { get; set; }
}
The naming of the columns might have been the problem.

Asp.Net Core EF Core Table Joins

I have two Objects, let says Employee and Address as follows
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public int DepartmentId {get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
}
The relationship model is that each employee belongs to a single department (using DepartmentID property)
I want to create a view which shows all employees and instead of displaying the department ID, the department name is shown instead
My controller looks like this
public class EmployeeController : Controller
{
private readonly IAppRepository _appRepository;
public EmployeeController(IAppRepository appRepository)
{
_appRepository = appRepository;
}
public ViewResult List
{
Department department;
// retrieve all employee from DBContext (SQL Server)
var model = _appRepository.GetEmployees;
// retrieve department details
department = _appRepository.GetDepartment(model.DepartmentId);
ViewBag.department = department;
}
}
My problem seems to be with the last two lines (//retrieve department details. as model is returning an enumerable of employee.
How can this be achieved, I was thinking using ViewModel but have not found a way yet. thanks
In my opinion, you could configure relationships between Employee and Department,refer to here
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public int DepartmentId { get; set; }
[ForeignKey("DepartmentId")]
public Department Department { get; set; }
}
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
}
In your repository GetEmployees, you could get all employees with their own Department using Include, refer to here
var employees = _context.Employees.Include(e => e.Department).ToList();
Then your model of var model = _appRepository.GetEmployees; will contains Department info for each employee and you could achieve name in view,for example
#model IEnumerable<Employee>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Department)
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Department.Name)
</td>
</tr>
}
</tbody>
</table>

How to show other model column in Razor view?

Currently i'm able to show all column in razer view from userRole model.
Just curious if i like to show the SiteName column in UserRole Razor view, instead of showing SiteID, is it possible? I know it can be done via custom view model, but is it a must? Please correct me if i'm wrong!
UserRole Model:
public int UserID { get; set; }
public string UserName { get; set; }
public int SiteID { get; set; }
*No SiteName column here....so i only can show SiteID in razor..
Site Model :
public int SiteID { get; set; }
public string SiteName { get; set; } <<-- the column i want..
Controller:
public ActionResult Index()
{
//need join table here perhaps?
return View(db.User_Role.ToList());
}
Razor View:
#model IEnumerable<FAB_Portal_POC.Models.UserRole>
<table class="table table-striped table-hover">
<tr>
<th>
#Html.DisplayNameFor(model => model.UserName)
</th>
<th>
#Html.DisplayNameFor(model => model.Role)
</th>
<th>
#Html.DisplayNameFor(model => model.SiteID)
</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Role)
</td>
<td>
#Html.DisplayFor(modelItem => item.SiteID) <<-- i want site name.
</td>
</tr>
}
</table>
The first problem is that your UserRole entity model doesn't seem to have a navigation property.
You don't need to, but then querying becomes awkward:
var rolesWithSite = from r in db.User_Role
join s in db.Sites on s.ID equals r.Site_ID
select new
{
Role = r,
Site = s
}
.ToList();
While when you add a navigation property (perhaps even instead of the foreign key property):
public class User_Role
{
public int UserID { get; set; }
public string Role { get; set; }
public string UserName { get; set; }
public virtual Site Site { get; set; }
}
Querying will become a whole lot easier:
var rolesWithSite = db.User_Role.Include(r => r.Site).ToList();
You can then introduce a viewmodel:
public class UserRoleViewModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string Role { get; set; }
public string Site { get; set; }
}
And map the query results to that:
var viewModels = rolesWithSite.Select(r => new UserRoleViewModel
{
UserID = r.UserID,
UserName = r.UserName,
Role = r.Role,
Site = r.Site.SiteName,
}).ToList();
return View(viewModels);
Entities and Models are very different.
You have to create a specific model for your page.
something like this :
PageModel
public string UserName { get; set; }
public string SiteName { get; set; }
In your controller your have to prepare all the data you want to send to the page. It s here that you will "associate" your entities to your pagemodel
public ActionResult Index()
{
var roles = db.User_Role.ToList();
var sites = db.Sites.ToList(); // i don t know how you get this information in your exemple.
//define here every property of the PageModel you want to use there
var model = new PageModel();
model.UserName = roles.UserName;
model.SiteName = sites.Select(...).SiteName;
return View(model);
}
then in razor view
#model IEnumerable<FAB_Portal_POC.Models.PageModel>
Hope it will help
You have multiple ways to do that the simple one is you have to create new class name UserRoleViewData and mention these fields and pass the class object to view. just like that.
New Class UserRoleViewData Look like this
public int UserID { get; set; }
public string UserName { get; set; }
public int SiteID { get; set; }
public string SiteName { get; set; }
and your Controller
public ActionResult Index()
{
List<className> dto = (from a in _db.User_Role
join b in db.Site on a.SiteID equals b.SiteID
select (new classname { UserID =a.UserID , UserName =a.UserName , SiteID =a.SiteID , SiteName =b.SiteName })).ToList();
return View(dto);
}
In View You have to change #model IEnumerable<FAB_Portal_POC.folderName.ClassName>
and map other property on view like others.I hope u'll understand and this will help you.
You can store the sites list in the ViewBag and show the site name by extracting it from the ViewBag:
public ActionResult Index()
{
ViewBag.Sites = db.Sites.ToList();
return View(db.User_Role.ToList());
}
And the View:
<td>
#Html.DisplayFor(modelItem => ViewBag.Sites.Single(s => s.SiteID == item.SiteID).SiteName)
</td>
Another, and to me a better approach, would be to create a ViewModel for your view which contains all of the data that you need. And return that view model to your view.
public class UserSiteViewModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string Role { get; set; }
public int SiteID { get; set; }
public string SiteName { get; set; }
}
No need to use ViewBag , join between 2 tables and store result in another viewModel it's vary bad idea. Please check out reference link, It will really help you to do Code in Very good direction. Click Here

MVC4 Foreign Key property is null

I have an Expenses and Categories tables. I have seeded the tables with few entries, but when pulled from DB the Category property is null. I would expect it to be automatically populated with entries from Category table. What am I missing?
Model:
public class Expense
{
public int ExpenseId { get; set; }
[DataType(DataType.Currency)]
public decimal Amount { get; set; }
[ForeignKey("CategoryId")]
public Category Category { get; set; }
[Required(ErrorMessage = "You must select some category!")]
public int CategoryId { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Description { get; set; }
public virtual ICollection<Expense> Expenses { get; set; }
}
View:
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Amount)
</td>
<td>
#item.CategoryId
</td>
<td>
#item.Category.Description //NullReferenceException here
</td>
</tr>
Controller:
// GET: Expenses
public ActionResult Index()
{
return View(db.Expenses.ToList());
}
Try marking the Category property as virtual. Navigation properties need to be virtual to support the lazy loading if it is not eager loaded.

Display list of related entities in MVC view

I have a Table ECmain that has a relationship with 2 other tables, Notes and Email.
I want to access the data in the view so that a list of toes will appear for every record in ECmain and be editable.
Here is my Model
namespace EditSuite.Models
{
public class ECmain
{
public ECmain()
{
this.Notes = new Collection<Notes>();
}
public int ID { get; set; }
public string Auth { get; set; }
public string KeyWords { get; set; }
public string Description { get; set; }
public string URL { get; set; }
public string Category { get; set; }
public string SubCategory { get; set; }
public string Title { get; set; }
public string Live { get; set; }
public virtual ICollection<Notes> Notes { get; set; }
public virtual ICollection<Email> Email { get; set; }
public List<Notes> NoteList { get; set; }
}
public class Email
{
public int ID { get; set; }
public int ECmainID { get; set; }
public string EmailText { get; set; }
public virtual ECmain ECmain { get; set; }
}
public class Notes
{
public int ID { get; set; }
public int ECmainID { get; set; }
public string NotesText { get; set; }
public virtual ECmain Ecmain { get; set; }
}
DB Context
namespace EditSuite.DAL
{
public class ECContext : DbContext
{
public ECContext()
: base("name=ECmodelConnection")
{
}
public virtual DbSet<ECmain> ECmain { get; set; }
public virtual DbSet<Email> Email { get; set; }
public virtual DbSet<Notes> Notes { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
Controller
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using EditSuite.DAL;
using EditSuite.Models;
namespace EditSuite.Controllers
{
public class ECmainsController : Controller
{
private ECContext db = new ECContext();
// GET: ECmain
public ActionResult Index()
{
//var church = from m in db.ECmain.Take(10)
// where m.Live == "Y" && m.Auth == "Yes"
// select m;
//church = church.OrderBy(m => m.Category);
return View(db.ECmain.Take(10));
}
View
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Notes)
</td>
<td>
#Html.DisplayFor(modelItem => item.KeyWords)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td>
#Html.DisplayFor(modelItem => item.URL)
</td>
<td>
#Html.DisplayFor(modelItem => item.Category)
</td>
<td>
#Html.DisplayFor(modelItem => item.SubCategory)
</td>
<td>
#Html.DisplayFor(modelItem => item.Title)
</td>
<td>
#Html.DisplayFor(modelItem => item.Live)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
#Html.ActionLink("Details", "Details", new { id=item.ID }) |
#Html.ActionLink("Delete", "Delete", new { id=item.ID })
</td>
</tr>
}
The result is
Server Error in '/' Application.
Invalid column name 'ECmain_ID1'.
Invalid column name 'ECmain_ID1'.
Invalid column name 'ECmain_ID'.
Invalid column name 'ECmain_ID1'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Data.SqlClient.SqlException: Invalid column name 'ECmain_ID1'.
Invalid column name 'ECmain_ID1'.
Invalid column name 'ECmain_ID'.
Invalid column name 'ECmain_ID1'
I tried
#Html.DisplayFor(modelItem => item.NoteList)
But that returned nothing
This sounds a lot like this problem:
ASP.NET MVC /Entity Framework Error - Invalid column name 'Environment_Id'
The answer there may be helpful to you.
Gerry
3rd Party Edit
For me the answer linked by Gerry could be of use to your problem. I have not worked much with EF and MVC but based on your Exception System.Data.SqlClient.SqlException: Invalid column name 'ECmain_ID1' or 'ECmain_ID' i assume
that the sql statement that is created by entity-framework does not match the columns in your database
or that the matching between table-columns from your sql-result and the properties of you email-object fails

Categories

Resources