I'm putting together an MVC application where I have created a base Model which then has four derived Models, all of which inherit from the base Model:
public abstract class BaseFund
{
public string Name { get; set; }
public int AccountId { get; set; }
public abstract decimal Value { get; }
public virtual InvestmentAccount Account { get; set; }
}
one of the derived Models:
public class ShareFund : BaseFund
{
public string ISIN { get; set; }
public ShareFundType FundType { get; set; }
public IncomeStatus IncomeStatus { get; set; }
public decimal TotalShares {
get
{
ICollection<ShareTransaction> tt = this.Transactions;
var outgoings = Transactions.Count > 0 ? Transactions.Where(t => t.TransactionType.IsOutgoing.Equals(true)).Sum(a => a.Units) : 0;
var incomings = Transactions.Count > 0 ? Transactions.Where(t => t.TransactionType.IsOutgoing.Equals(false)).Sum(a => a.Units) : 0;
return incomings - outgoings;
}
}
public override decimal Value
{
get
{
return this.TotalShares * (this.SharePrice / 100);
}
}
public decimal SharePrice { get; set; }
public ICollection<ShareTransaction> Transactions { get; set; }
}
And there are three other derived Models that are similar. All of the Models are POCO's used by Entity Framework.
EDIT : The view is standard MVC scaffolding stuff at this stage:
<table>
<tr>
<th>
Name
</th>
<th>
Account
</th>
<th>
Value
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Account.AccountNumber)
</td>
<td>
#Html.DisplayFor(modelItem => item.Value)
</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>
What I want to do is create a View that shows the fields from the Base Fund (Name, AccountId and Value). The problem is that the logic that works out the Value is different for every derived Model - in the case of ShareFund it uses TotalShares, so the BaseFund used in the View must be cast to be of type ShareFund. The other derived Models don't necessarily have TotalShares as a property.
With that in mind:
Is using inheritance with Models in this way the way to go? If so, how do I get the fields specific to the derived models in the View?
If using inheritance is not recommended for this type of scenario, what should I use in its place?
Thanks
There turned out to be a simple answer to this. One of the Transaction properties further down the chain wasn't being populated from the database by EF. This meant TransactionType was null which led to an null reference error in TotalShares. I misread this as meaning there was a problem with the property as it belonged to the derived Model and not the base Model.
Thanks Lazarus, your comment led me to the problem.
Related
I have .NET 5 project with Index page where is table containing all rows from table Contracts, each row has Contract_StatusID column where is saved FK from table Contract_Statuses - table Contracts has more columns with this logic
Thing is, that Explicit loading is loading fully only last row of the table, other row's columns are randomly null, even when collection is .Include(s => s.Contract_Status) from controller code.
All ID columns are set, and these IDs are existing in related tables, but not loaded into this table on Index page.
Detail page always correctly load all .Include() relation tables but not on Index page.
I've validated my models and they looks OK, and work within other pages of the site.
What I am missing? Already went through MSDN and EFCore tutorials for this, but it just don't work.
public async Task<IActionResult> Index()
{
var contract = await context.Contracts
.Include(c => c.Employee)
.ThenInclude(c => c.Gender)
.Include(i => i.Business_Unit)
.Include(i => i.Brand)
.Include(i => i.Contract_Status)
.Include(i => i.Statutory_Unit)
.Include(i => i.Management_Market).ToListAsync();
return View(contract);
}
Edit: Adding my Index.cshtml
#model IEnumerable<MyProject.Models.Contract>
<table class="table table-hover">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Employee)
</th>
<th>
#Html.DisplayNameFor(model => model.Contract_Status.Contract_Status)
</th>
<th>
#Html.DisplayNameFor(model => model.Business_Unit.Business_Unit)
</th>
<th>
#Html.DisplayNameFor(model => model.Brand.Brand)
</th>
<th>
#Html.DisplayNameFor(model => model.Statutory_Unit.Statutory_Unit)
</th>
<th>
#Html.DisplayNameFor(model => model.Management_Market.Management_Market)
</th>
<th></th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Employee.EmployeeID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Contract_Status.Contract_Status)
</td>
<td>
#Html.DisplayFor(modelItem => item.Business_Unit.Business_Unit)
</td>
<td>
#Html.DisplayFor(modelItem => item.Brand.Brand)
</td>
<td>
#Html.DisplayFor(modelItem => item.Statutory_Unit.Statutory_Unit)
</td>
<td>
#Html.DisplayFor(modelItem => item.Management_Market.Management_Market)
</td>
<td>
<a asp-action="Edit" asp-route-id="#item.ContractID">Edit</a> |
<a asp-action="Details" asp-route-id="#item.ContractID">Details</a>
</td>
</tr>
}
</tbody>
</table>
And my Contract model with example of related table
public class Contract
{
[Key]
[Required]
public Guid ContractID { get; set; }
[Required]
public string EmployeeID { get; set; }
public Employee Employee { get; set; }
[Required]
public int Contract_StatusID { get; set; }
public CONF_Contract_Status Contract_Status { get; set; }
[Required]
public int Business_UnitID { get; set; }
public CONF_Business_Unit Business_Unit { get; set; }
[Required]
public int BrandID { get; set; }
public CONF_Brand Brand { get; set; }
[Required]
public int Statutory_UnitID { get; set; }
public CONF_Statutory_Unit Statutory_Unit { get; set; }
[Required]
public int Management_MarketID { get; set; }
public CONF_Management_Market Management_Market { get; set; }
}
public class CONF_Contract_Status
{
[Key]
public int CS_ID { get; set; }
[Required]
[Display(Name = "Status")]
public string Contract_Status { get; set; }
[Required]
public bool Enabled { get; set; }
public Contract Contract { get; set; }
}
DB screenshot of Contract table
and CONF_Contract_Status
I usually alway create view model for index view, since it is usually needed several string columns, but it can have a lot of columsn. YOu don't need to bring from the server the whole record if you only need one column
public class ContractVieModel
{
public Guid ContractID { get; set; }
public string EmployeeName { get; set; }
public string Contract_Status { get; set; }
public string Business_Unit { get; set; }
.... and so on
}
action, you don't to use Include explicetly
public async Task<IActionResult> Index()
{
var model = await context.Contracts
.Select(i=> new ContractViewModel
{
Id=i.Id,
EmployeeName=i.Employee.Name
Contract_Status =i.Contract_Status.Contract_Status,
Business_Unit = i.Business_Unit.Business_Unit,
... and so on
}).ToListAsync();
return View(model);
}
view
#model IEnumerable<ContractViewModel>
......
try it , and if it is still not working then you will have to use left outer joins to get a view model
I have issue with reading related data in my Entity framework core in ASP.NET Core 2.2.
I have two entities Team and Player and because I deal with many to many relationship (team can have many players and players can have multiple teams), I have junction entity PlayerAssignments.
In players Index view, I want to show all players and in information about them - first name, last name and the teams they are assigned to, but I don't know how and I am trying to solve this for about two days now.
public class Player
{
public int id { get; set; }
public string first_name { get; set; }
public string last_name { get; set; }
public ICollection<PlayerAssignments> PlayerAssignments { get; set; }
}
public class Team
{
public int id { get; set; }
public string name { get; set; }
public string city { get; set; }
public ICollection<PlayerAssignments> PlayerAssignments { get; set; }
}
public class PlayerAssignment
{
public int PlayerID { get; set; }
public int TeamID { get; set; }
public Player Player { get; set; }
public Team Team { get; set; }
}
everything similarly in dbcontext.
In my index method of PlayerController.cs:
var player = await _context.Player
.Include(one => one.PlayerAssignment)
.ThenInclude(team => team.Team)
.ThenInclude(teamName => teamName.name)
.ToListAsync();
if(player == null)
{
return NotFound();
}
return View(player);
}
My index view looks like this:
<table class="table">
<thead>
<tr>
<th>
First name
</th>
<th>
Last name
</th>
<th>
Player's teams
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.first_name)
</td>
<td>
#Html.DisplayFor(modelItem => item.last_name)
</td>
<td>
foreach(var assignedTeam in item.PlayerAssignments)
{
#* I just cant figure out code here *#
}
</td>
</tr>
</tbody>
<table>
I need to show all players with their info and the teams they are assigned to. But no matter what I tried, I never managed to write out the teams the players are assigned to (they are filled in database).
First of all your second thenInclude is redundant because that variable already is being included in the Team Object so change to this:
var player = await _context.Player
.Include(one => one.PlayerAssignment)
.ThenInclude(team => team.Team)
.ToListAsync();
if(player == null)
{
return NotFound();
}
return View(player);
And now all you have to do is display that Team 'Name' variable in the foreach loop which contains all the player team assigments like so:
<td>
foreach(var assignedTeam in item.PlayerAssignments)
{
#assignedTeam.Team.name
}
</td>
In one of my views I have a series of child tables, where the child table has a related lookup- ie not a grandchild table. The related lookup is a description for a code. In standard asp.net mvc, I can point to that related table and display the description in that child record. In .net core it comes up as blank. If I just display the code in the child records it works.
Am I missing something?
Code for model, controller and view below:
Traditional asp.net mvc that works:
Model
Model
namespace ServeMeHR.Models
{
using System;
using System.Collections.Generic;
public partial class TeamAssignmentHistory
{
public int Id { get; set; }
public string AssignedBy { get; set; }
public System.DateTime DateAssigned { get; set; }
public int ServiceRequest { get; set; }
public int Team { get; set; }
public virtual ServiceRequest ServiceRequest1 { get; set; }
public virtual Team Team1 { get; set; }
}
}
Relevant Controller portion
public async Task<ActionResult> Details(int? id)
{
ViewBag.FileUp = db.ApplicConfs.Select(s => s.FileSystemUpload).FirstOrDefault();
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
ServiceRequest serviceRequest = await db.ServiceRequests.FindAsync(id);
if (serviceRequest == null)
{
return HttpNotFound();
}
return View(serviceRequest);
}
Relevant view portion
<div id="tabs-2">
<table class="table">
<tr>
<th>Assigned BY</th>
<th>Date Assigned</th>
<th>Team</th>
</tr>
#foreach (var item in Model.TeamAssignmentHistories)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.AssignedBy)
</td>
<td>
#Html.DisplayFor(modelItem => item.DateAssigned)
</td>
<td>
#Html.DisplayFor(modelItem => item.Team1.TeamDescription)
</td>
</tr>
}
</table>
</div>
For .Net Core -This doesnt work
Model
using System;
using System.Collections.Generic;
namespace ServeMeHRCore21.Models
{
public partial class TeamAssignmentHistories
{
public int Id { get; set; }
public string AssignedBy { get; set; }
public DateTime DateAssigned { get; set; }
public int ServiceRequest { get; set; }
public int Team { get; set; }
public ServiceRequests ServiceRequestNavigation { get; set; }
public Teams TeamNavigation { get; set; }
}
}
relevant controller code
Relevant part of controller
// GET: ServiceRequests/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var serviceRequests = await _context.ServiceRequests
.Include(s => s.MemberNavigation)
.Include(s => s.PriorityNavigation)
.Include(s => s.RequestTypeNavigation)
.Include(s => s.RequestTypeStepNavigation)
.Include(s => s.StatusNavigation)
.Include(s => s.TeamNavigation)
.Include(s => s.FileDetails)
.Include(s => s.ServiceRequestNotes)
.Include(s => s.StepHistories)
.Include(s => s.TeamAssignmentHistories)
.Include(s => s.IndividualAssignmentHistories)
.AsNoTracking()
.SingleOrDefaultAsync(m => m.Id == id);
if (serviceRequests == null)
{
return NotFound();
}
return View(serviceRequests);
}
Relevant part of the view
<div id="tabs-2">
<table class="table">
<tr>
<th>Assigned BY</th>
<th>Date Assigned</th>
<th>Team</th>
</tr>
#foreach (var item in Model.TeamAssignmentHistories)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.AssignedBy)
</td>
<td>
#Html.DisplayFor(modelItem => item.DateAssigned)
</td>
<td>
#Html.DisplayFor(modelItem => item.TeamNavigation.TeamDescription)
</td>
</tr>
}
</table>
</div>
The above view shows blank for the team description. The code for each of these is almost identical...
Depending on what version of EF Core you're running, there's various solutions. In EF 6 (what you're using in ASP.NET MVC), reference/collection properties with a virtual keyword participate in lazy-loading. EF dynamically subclasses your entity class, overriding the reference/collection properties (which is why virtual is necessary) and adds logic to query the database when the getter is hit. Then, when you access this property in your application, if the relationship is not available, EF transparently queries for it and fills it in, giving the semblance that it's always set and ready to go.
Before EF Core 2.1, lazy-loading was not an option at all. To have these properties have values, you need to either eagerly or explicitly load them:
Eager loading
var foo = await _context.Foos.Include(x => x.Bar).SingleOrDefaultAsync(x => x.Id == fooId);
Explicit loading
var foo = await _context.Foos.SingleOrDefaultAsync(x => x.Id == fooId);
await _context.Entry(foo).Reference(x => x.Bar).LoadAsync();
// use `Collection` rather than `Reference` for a collection type
EF Core 2.1 now supports lazy-loading, but you still need the virtual keyword, which you currently don't have on your entity class reference/collection properties. Also, you need to add in the appropriate services in Startup.cs:
services.AddDbContext<MyContext>(
b => b.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString));
However, I'd advise you to avoid going that route, especially since you don't seem to be aware that any of the stuff I mentioned was actually happening under the hood back in EF 6. It's very easy with lazy-loading to create all sorts of issues like N+1 queries that can cause major problems for you application. It's almost always better to eagerly load any related entities, so you can combine queries. Just because EF Core now supports lazy-loading, doesn't mean you need to use it.
I have the following code. I want to use the objects of List in my view where I have used DTOQuestions
//My Model Question
public class Question
{
public int Question_Id { get; set; }
public string Question_Title { get; set; }
public List<Reply> Reply_List { get; set; }
}
//My 2nd Model Answer
public class Reply
{
public int Reply_Msg { get; set; }
public int Attachment_Id { get; set; }
}
//View
#foreach(var item in Model)
{
foreach(var replylbl in item.Forum_Reply_List)
{
<th>#Html.DisplayNameFor(replymodel => replylbl.Reply_Msg)</th>
<th>#Html.DisplayNameFor(replymodel => replylbl.Attachment_Id)</th>
}
}
#foreach (var item in Model) {
<tr>
<td>
#foreach (var reply in item.Forum_Reply_List){#Html.DisplayFor(modelReply => reply.Reply_Msg)}
</td>
<td>
#foreach (var reply in item.Forum_Reply_List){#Html.DisplayFor(modelReply => reply.Attachment_Id)}
</td>
I have these two models and following view. Now I want to access Answer (reply) model's objects in my view which has model type Question
But in view I am unable to display headers for those columns..
I have a controller in my MVC sire that has a couple of methods along the lines of the following:
public ActionResult _GetServiceStatus()
{
...
}
public ActionResult _GetEventLogErrors()
{
...
}
Each of these methods references a different class type that I have stored in my Model, Service and Event respectively. These can be seen below:
public class Service
{
public string Name { get; set; }
public string Status { get; set; }
}
public class Event
{
public string Source { get; set; }
public string EntryType { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
}
What I want to do is to show the results of these methods on a single view. I already have this working for the Services check, with the results displaying correctly, but I cannot find how to add in the Event results.
What I have currently in my view is below:
#{
ViewBag.Title = "Monitoring";
}
#model IEnumerable<SystemMonitoringTool.Models.Monitoring.Service>
<div style="width:25%">
<span>
Services
</span>
<table id="services" style="border:solid; border-width:2px; width:100%">
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(model => item.Name)
</td>
<td>
#Html.DisplayFor(model => item.Status)
</td>
</tr>
}
</table>
</div>
Can someone help me find a way to display these side by side on the same view? Will be happy to provide more info if needed.
Two options:
Use child actions. Instead of directly going to one of these individual controller actions, instead add an action like:
public ActionResult Monitoring()
{
return View();
}
You'll notice this action doesn't do much. It's just rendering a view. Then you'll need to move your HTML for services/events into partial views. For example, _GetServiceStatus.cshtml and _GetEventLogErrors.cshtml, where the model for each will be a collection of your Service or Event types, respectively. Finally, in your Monitoring.cshtml view (based on the action name above), you'll add:
#Html.Action("_GetServiceStatus")
#Html.Action("_GetEventLogErrors")
This view doesn't need a model, because it's not directly working with anything.
Use a view model that encapsulates your two collections:
public class MonitoringViewModel
{
public List<Service> Services { get; set; }
public List<Event> Events { get; set; }
}
Then you'll still need a unifying action. But here, you'll populate both lists. Basically, you'll just be moving your two existing actions into one:
public ActionResult Monitoring()
{
var model = new MonitoringViewModel
{
Services = /* code to retrieve services */,
Events = /* code to retrieve events */
}
return View(model);
}
Then, you can iterate through each list independently to build your HTML:
Monitoring.cshtml (again, based on the action name)
#model Namespace.To.MonitoringViewModel
...
<table id="services" style="border:solid; border-width:2px; width:100%">
#foreach (var item in Model.Services) { // notice the change here
<tr>
<td>
#Html.DisplayFor(model => item.Name)
</td>
<td>
#Html.DisplayFor(model => item.Status)
</td>
</tr>
}
</table>