LINQ .Include() - asp.net-mvc

I'm trying to write some LINQ to join these tables but it's giving me errors. Here's my code
public PartialViewResult Getjobs(int id)
{
using (var db = new MTEntities())
{
var jobs = db.jobs.Include(j => j.jobslist)
.Include(jl => jl.jobslist.model_jobslist
.Where(aa=>aa.jobslist.JobsListID==jl.jobslist.model_jobslist.JobsListID))
.Where(bki => bki.BookingID==id)
.ToList();
if (jobs != null)
{
return PartialView("_JobDetails", jobs);
}
I'm trying to display the approx time from the model_joblist table for the incoming booking id.
<th>
#Html.DisplayNameFor(model =>model.jobslist.model_jobslist.ApproxTime))
</th>
Is this possible?
Error 5 'System.Collections.Generic.ICollection' does not contain a definition for 'JobsListID' and no extension method 'JobsListID' accepting a first argument of type 'System.Collections.Generic.ICollection' could be found (are you missing a using directive or an assembly reference?) Controllers\BookingController.cs 90 97 MechanicTracker
thats the error

There are multiple errors here. Firstly, I'd advise looking up include as include is not used for joins. You don't need it here at all to make the query work, but it will be useful for performance reasons.
Secondly, jobs being passed into _JobDetails will be of type List<job> so #Html.DisplayNameFor(model =>model.jobslist.model_jobslist.ApproxTime)) is wrong.
Thirdly, if you want to display the value of ApproxTime you need #Html.DisplayFor not #Html.DisplayNameFor. However, as the snippet is in a th element maybe you did want DisplayNameFor and you haven't posted the relevent bit of code at all.
To create your query you can just use:
var jobs = db.jobs.Where(bki => bki.BookingID==id).ToList();
Add Includes later if you need them for performance. See links at end of answer for help on this.
Then for display it will depend on exactly what you want. You will have a list of jobs so it may be something like:
foreach (var job in Model) {
...Some HTML...
job.jobslist.model_jobslist.ApproxTime.ToString("whatever date format you want")
...Some more HTML...
}
Some useful links:
Date and time formats
How to use the include statement
How to use the join statement

Related

Same query multiple times executed defered execution

I'm building a very simple CRUD web-application (ASP.NET MVC) for tennisplayers and the tournaments they can participate.
On a specific page I want to show all tournaments in the database with a title at the top saying 'All Tournaments' with between brackets the amount of records in the database.
My cshtml would look like this:
#model System.Collections.Generic.IEnumerable<TMS.BL.Domain.Tournament>
#{
ViewBag.Title = "All Tournaments";
Layout = "_Layout";
}
<h3>All Tournaments (#Model.Count())</h3>
#if (!#Model.Any())
{
<p>No tournaments were found...</p>
}
else
{
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Starts</th>
<th scope="col">Ends</th>
<th scope="col">Org. Club</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
#foreach (var t in Model)
{
<tr>
<td>#t.Name</td>
<td>#t.StartDate.ToString("ddd, dd/MM/yyyy")</td>
<td>#t.EndDate.ToString("ddd, dd/MM/yyyy")</td>
<td>#t.OrganizingClub.Name (#t.OrganizingClub.Province - #t.OrganizingClub.Town)</td>
<td>
<a asp-controller="Tournament" asp-action="Details" asp-route-id="#t.Id" class="btn btn-primary btn-sm">Details</a>
</td>
</tr>
}
</tbody>
</table>
}
}
The controller for this page is the TournamentController. This controller is using a Manager object which contains the dbcontext. The GetAllTournamentsWithOrgClubAndParticipants() method returns an IEnumerable of tournament objects (with an include of the club and pariticpants, but for my question this is of no importance).
public class TournamentController : Controller
{
private IManager _mgr;
public TournamentController(IManager manager)
{
_mgr = manager;
}
public IActionResult Index()
{
return View(_mgr.GetAllTournamentsWithOrgClubAndParticipants());
}
When I load the page, I see that the same query is fired 3 times. once for the #Model.Count() in the title of the webpage, once for the #Model.Any() to determine wheter or not to show the table and once in the foreach loop. Now I know this is because of defered excecution and I could resolve this by adding a ToList() behind the GetAllTournamentsWithOrgClubAndParticipants() in the controller class, but I often hear NOT TO USE the ToList() method because of the way you are loading everything into memory doing this. To my feeling for this case it's still better than excecuting the same query 3 times in a row or am I wrong? Any other way I could resolve this?
Thank you very much!
By returning IEnumerable you are telling the caller that it will get something that can be enumerated over, it does not set the underlying type. So for instance if your manager/repository method returns:
var result = context.Tournaments.Include(t => t.OrganizingClub).Include(t => t.Participants);
return result;
then what is sent back is effectively an EF Query that can be enumerated over. Your Razor code-behind will effectively execute it each time to get the Count, Any, and then to iterate over with your foreach. This should execute 3 slightly different queries. The first being a SELECT COUNT(*) FROM... the second being a IF EXISTS SELECT TOP (1) FROM... then the SELECT t.Id, t.Name, ... FROM with the same filters and joins.
Changing this to:
var result = context.Tournaments.Include(t => t.OrganizingClub).Include(t => t.Participants).ToList();
Will load the details into memory once, then the Count and Any will just be in-memory operations. This in itself for this example is not bad, but it is worth understanding the potential consequences of operations like this. When you're dealing with a data volume that will be manageable on a single screen (I.e. 10's of records, not 1000's+) then there is essentially no harm in returning a materialized list of data. However, as systems grow, design decisions based on smaller data sets can come back to bite you in the butt. For instance, if you want to introduce pagination for results. You'll want the population to the view to be running a query that ultimately loads and returns just one page of data, not loading all rows to send one page to the view.
Even with dealing with smaller sets of data it is worthwhile to understand and utilize EF's projection capability using Select to populate view models. In your example you will be loading all data from the Tournament, OrganizationClub, and Participants even though your view only needs a handful of fields. This can also open the door for unexpected future performance hits because you are serializing entities. If we later add another navigation property or collection to a Tournament, Club etc. and even if this view doesn't need that additional property/collection, simply sending a Tournament to the view could result in the serializer "touching" that navigation property and triggering a lazy load. (Extra queries) All the sudden a new requirement for one area of the application has a performance impact in many other areas you didn't even touch.
Looking at the View code:
<td>#t.Name</td>
<td>#t.StartDate.ToString("ddd, dd/MM/yyyy")</td>
<td>#t.EndDate.ToString("ddd, dd/MM/yyyy")</td>
<td>#t.OrganizingClub.Name (#t.OrganizingClub.Province - #t.OrganizingClub.Town)</td>
<td>
<a asp-controller="Tournament" asp-action="Details" asp-route-id="#t.Id" class="btn btn-primary btn-sm">Details</a>
</td>
We need a Tournament Name, Startdate, EndDate, Organizing Club Name, Province, and Town, plus the Tournament ID.
This can be simplified down to a minimal view model called for example TournamentSummaryViewModel:
[Serializable]
public class TournamentSummaryViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string OrganizingClubName { get; set; }
public string ClubProvince { get; set; }
public string ClubTown { get; set; }
}
then to project this:
var result = context.Tournaments
.Select( t => new TournamentSummaryViewModel
{
Id = t.Id,
Name = t.Name,
StartDate = t.StartDate,
EndDate = t.EndDate,
OrganizingClubName = t.OrganizingClub.Name,
ClubProvice = t.OrganizingClub.Province,
ClubTown = t.OrganizingClub.Town
}).ToList();
The advantages of this is that it minimizes the query to just the columns the view will need. This avoids surprises as the data model changes/grows over time as we don't serialize entities so there are no lazy load risks. When using projection with Select (Or Automapper's ProjectTo) you don't even need to worry about eager loading /w Include. It also reduces the payload size to the view, and helps hide the overall domain structure of your application from people peeking and even tampering with the data being sent via browser debugging tools.
In the above example we just issue the ToList() call on the assumption that the data volume will be reasonable, but we could just as easily let it return the IQueryable for the Razor code to interact with. You can map fields to flatten data (such as adding details from the OrganizingClub) or consolidate data such as if you want ParticpantCount = t.Paricipants.Count(), or nest additional view models Selected from related data. The key is to avoid embedding the entities themselves in the returned ViewModel. (As they can form a ticking time bomb.)

MVC accessing linked table value using entity framework

I am new to entity framework and I am trying to get my head around it. I am used to writing stored procedures which have all the data I need on a example by example basis.
I am under the impression that I can get all values from a particular table including the foreign key values direct using entity framework without having to write a select query which joins the data.
I have the following in my controller
public ActionResult Patient()
{
using (var context = new WaysToWellnessDB())
{
var patients = context.Patients.ToList();
return View(patients);
}
}
In my view I have the following
#foreach (var item in Model)
{
<p>
#item.FirstName #item.Surname #item.Gender.GenderDesc
</p>
}
I have two tables, Patient and Gender, GenderId is a foreign key which I am trying to get the GenderDesc from that table.
I am getting the following message
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
Can someone explain why I cannot access GenderDesc. It does work if I remove the using() around my context, but I don't really want to leave that open, is there a way to get this to work still having the using around?
Thanks in advance.
Correct, you have disposed of the context as it is within a using statement, so anything you try to access from then on will not be able to be lazy loaded. The disadvantage with lazy loading is that it will perform a query for the gender for every patient you are iterating over, which is handy, but bad! I would load the related table at query time using Include.
You'll need a new import:
using System.Data.Entity;
And then include the related table:
var patients = context.Patients.Include(p => p.Gender).ToList();
That will result in a query which will join to your "Gender" table and you should be able to output item.Gender.GenderDesc in your view.

What's the ideal approach to table column order based on preference

Data Tables in my application have lots of columns. Different users want different set of columns and in a particular order. The way I'm doing is, I have unique ID associated with each column for a table and I'm storing them in user preference. For example:
columnsToDisplay = "1,4,23,12,2,5,6,7,8,13,15"
In my view I'm using if else if to iterate though my table model (I'm using ASP.NET MVC) to render the table. I feel this is not the right way to do. Imagine a table with 50 columns and doing if else if 50 times! What's the ideal approach for this problem without using jQuery or any client side script plugin?
EDIT:
Here is what I'm doing now. I'm comparing each column with use preference.
foreach(var col in model)
{ if(col.name == id) {
<td>{{id}}</td>}else if(col.name == customerName) {
<td>{{name}}</td>}else if(col.name == balance) {
<td>{{balance}}</td>}else if(col.name == createdOn) {
<td>{{createdOn}}</td>}
}
.....
and so on...
If these tables are readonly (you don't need to be able to post them back to the server), you could probably get away with throwing your model data into an array of IDictionary objects. Then you could use the values of your columnsToDisplay (as an array of ints) to get the relevant column when creating the view.
Something like this, assuming your model is called model:
model.ColumnArray = { 2, 4, 6, 1, 0 };
Then, assuming the model has a property called Rows that has your IDictionary objects:
#foreach(var model in Model.Rows)
{
<tr>
foreach(var column in Model.ColumnArray)
{
<td>#model[column]</td>
}
</tr>
}

Using .where extension method causes duplicates (MVC-LINQ)

I am trying to create a partial view to display some data. My controller takes in a string itemID and performs a query based on that. When the partial view is called, it displays the same record over and over again.
Note: I changed the name of objects for security purposes.
[ChildActionOnly]
public ActionResult someAction(string itemID = "")
{
//Empty itemID
if(string.IsNullOrEmpty(itemID))
{
return RedirectToAction("Index", "Search");
}
var model = _db.someTable
.Where(r => r.itemID == itemID)
.OrderBy(r => r.col1)
.ThenBy(r => r.col2)
.Take(20);
return PartialView("_myView", model);
}
I have tried removing the OrderBy and ThenBy methods, but the result remain the same, (Order would not matter since they are duplicates...). When I remove the .Where method, it works as expected and displays 20 different records (though, not filtered by any means).
My view was created by Visual Studio using the List template. The view been proven working by removing the .Where method from the LINQ statement. Here are the important bits of the view:
#model IEnumerable<MyApp.Models.OperationData>
.
.
.
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.some column)
</td>
.
.
.
Any ideas as to what is wrong with this query?
EDIT: Tried the other LINQ syntax and ended up with the same results:
var model = from r in _db.someTable where r.itemID == itemID select r;
Winner winner chicken dinner!
Turns out the issue was with the mapping of model to table. The table I was working on has a composite key, which I didn't know about... After reading mipe34's bit about primary keys, I decided to do some investigation into the table structure. After discovering the composite keys, I added the mapping for the 2nd key and all works well.
This issue was extremely confusing since the SQL generated by LINQ worked perfectly fine when run in SQL Management Studio.
Thanks all!
Hit the breakpoint just after the model variable and see what SQL query is generated by LINQ - VS should display it for you. You can also try to execute the query (add .ToList() at the end of the query) to see the actual result, what is in the collection to distinguish if there is a problem in query or view.

.net MVC 3 displaying values in a many-to-one virtual object

I'm creating an expense report project - my first using MVC.
This is a Database First project and I'm using Oracle ODP.
I have an entity model with the following classes:
ExpenseReport
ExpenseItem
ExpenseType
The expense report will have many expense items.
Each expense item will be of a specific expense type from the list of types in that ExpenseType class - thus a many-to-one relationship.
A single expense type record contains for each type a category, and headings for description/comment field to go with that type.
In my view, I am able to display the report with a list of all the expense items for that report. I am doing this through my Edit or Details controllers with the following code:
public ActionResult Details(long id)
{
using (var db = new Entities())
{
var thisReport = db.ExpenseReport.Find(id);
thisReport.expItems = db.ExpenseItem.Where(e => e.BB_EXPREPORT_ID == id).ToList();
return View(thisReport);
}
}
I tried adding this to the code (just above the return View line) to also include the expense type values (category, headings) for each type but it is failing due to a casting issue - cannot implicitly convert (are you missing a cast?)
foreach (ExpenseItem item in thisReport.expItems)
{
item.expType = db.ExpenseType.Where(e => e.BB_EXP_TYPE == item.BB_EXP_TYPE);
}
My questions:
Isn't there a way I can set up my model classes so that I don't need
to add these statements? I.E. Can't I modify the virtual object Get
statement to pull them there? Or can I modify the entitymodel file
to get these values? Is it a loading issue? I turned off lazy
loading.
If there is not a way to do this at the model level so that virtual
objects are included in the get, then how can I set the cast in my
code above to pull the values from the ExpenseType table for that
given expense type?
Thanks.
Admittedly, I'm not sure of what you're trying to do, but it looks like you just want to load up your object all at once. You can do this with Entity Framework using the Include method.
I'd assume you're using the "pluralization" feature for naming your db sets, but then again, your call to "db.ExpenseReport" is missing an (s) at the end.
public ActionResult Details(long id) {
using (var db = new Entities()) {
ExpenseReport thisReport = db.ExpenseReport.Include("ExpenseItems.ExpenseType").Single(id);
return View(thisReport);
}
}
Then you can use it in your Razor view like so.
<ul>
foreach (var item in Model.ExpenseItems) {
<li>Name: #item.YourExpenseItemName - Type: #item.ExpenseType.YourTypeName</li>
}
</ul>

Resources