I have two database tables that are linked by an attribute in one of the tables. The tables are being separately represented as models in an ASP .NET core MVC app. In a controller action, I have a linq query that performs a join on the two tables and selects a set of columns from the join results. I'm trying to send this result set to a view page so that the data can be displayed in a paginated table, but I'm not sure how exactly this should be done.
In the past, when using ASP .NET MVC (not core), I've been able to execute stored procedures that return result sets in controller actions, iterate through the result sets, build up lists with the data and then store the lists in the viewbag which can be accessed in the view. I've tried to directly store the EntityQueryable object in the viewbag but I got an error and I'm not sure how I would go about iterating through it anyway.
What would be the best way to send the data returned from the linq query to the View page?
Controller Action code:
var resultsObj = (from rd in _db.ResData
join ra in _db.ResAvailability on rd.RecNo equals ra.RecNoDate
where ra.TotalPrice < Int32.Parse(resDeals.priceHighEnd) && ra.TotalPrice > Int32.Parse(resDeals.priceLowEnd)
select new
{
Name = rd.Name,
ImageUrl = rd.ImageUrl,
ResortDetails = rd.ResortDetails,
CheckIn = ra.CheckIn,
Address = rd.Address,
TotalPrice = ra.TotalPrice
}).Take(10);
ViewBag.resultSet = resultsObj;
EDIT:
My query is returning data that from multiple tables (since it is a join) so data from the query results has to
be extracted and separated into the two different viewmodels which correspond to the tables in the join.
The first viewmodel represents each row of the query results. The second viewmodel is just a list to hold all of the rows that are contained in the query results.
I'm trying to understand how to do the data extraction from the query results into the viewmodels as I have explained here.
I would return a ViewModel instead. Generally you may use it to send the data to/from the View and controller.
I am currently working on a big project, and ViewModels work pretty well for me.
Check this short video:
https://www.youtube.com/watch?v=m086xSAs9gA
UPDATE
I am assuming that your query works properly (I did not read it).
To send your data via a ViewModel to the View.
First create the required ViewModel classes:
public class PageNameViewModel
{
public string Name { get; set; }
public IEnumerable<ResortDetailViewModel> ResortDetailViewModels { get; set; }
... rest of properties are not shown for clarity ...
}
public class ResortDetailViewModel
{
public string Detail1 { get; set; }
public int Detail2 { get; set; }
... etc. ...
}
Now use the ViewModels in the controller (or let us say, fill the data in the viewmodel):
var viewModel = (from rd in _db.ResData
join ra in _db.ResAvailability on rd.RecNo equals ra.RecNoDate
where ra.TotalPrice < Int32.Parse(resDeals.priceHighEnd) && ra.TotalPrice > Int32.Parse(resDeals.priceLowEnd)
.Take(10)
select new ClassNameViewModel
{
Name = rd.Name,
ImageUrl = rd.ImageUrl,
ResortDetailViewModels = rd.ResortDetails.Select(o =>
new ResortDetailViewModel
{
Detail1 = o.detail1,
Detail2 = o.detail2,
... etc. ...
},
CheckIn = ra.CheckIn,
Address = rd.Address,
TotalPrice = ra.TotalPrice
});
return View(viewModel);
Now you can use the ViewModel in the View (I assume you know how, as you watched the view I linked).
Notice that ViewModels should ideally hold primitive data in this case (that will make your life easier if you plan later to serialize the ViewModel and send it to another client).
In the above code, I converted any complex types to primitive types, and that should be on each element (notice I did the same on ResortResults, as I converted them to an array of ViewModel, i.e., array of an objects that only holds primitive data, so no deep hierarchy).
I also moved Take(10) to the upper side of the code, so you do not need to create a ViewModel for each element, and then take only 10! that is just wasting of performance for nothing. By moving it to the upper side, we take the 10 elements before creating the ViewModels, i.e., we create the ViewModels for only the required elements.
I hope that helps. If you need any further help, please please tell me.
Related
I am currently preparing my Application to transition over to MVC and in doing so have replaced all SQL statements with LINQ (EF) and removing all Datasets/DataTables, replacing them with strongly typed Lists.
I am stuck on one scenario where I need to pivot a strongly typed List<T> , after I pivot (number of columns produced vary) I am attempting to re-assign the results back to the GridView, keeping in mind that I don't want to use a DataTable.
I have looked at various examples where people are attempting to use ExpandoObject but I can't get it to work and continue to get this error:
The data source for GridView with id 'GridReport' did not have any properties or attributes from which to generate columns. Ensure that your data source has content.
The alternative would be to create some kind of class dynamically with properties getter and setter, would this be the right approach?
Given that eventually I will discard GridView too in MVC (controls not supported) I am now just thinking to maybe create an output just using HTML table? Since all I am doing is outputting the display and not using the GridView for any other purpose.
Some guidance and code example would help for the right scenario.
My List <T> looks like this (shortened for simplicity) and I pivot on ticker_id using a GroupBy. Am I able to return the property names from the Linq query too? if so how?:
public class CorporationCompare
{
public int ticker_id { get; set; }
public string tickerSymbol { get; set; }
public decimal? price { get; set; }
}
//pivot
var query = (from item in lstCompareCorp
let key = new { ticker_id = item.ticker_id }
group new { tickerSymbol = item.tickerSymbol, price = item.price } by key)
.ToList();
Before Pivot:
ticker_id tickerSymbol price
1 GOOG 123.45
208 AAPL 543.21
After Pivot:
ticker_id 1 208
tickerSymbol GOOG AAPL
price 123.45 543.21
I'm having a problem with an application I made for my company. We are taking queries out of an ERP system. People can search for an article, and then the application shows them all relevant technical data, pictures and/or datasheets.
Problem is: it's loading very slow. Queries seem to run fine, but the generation of the view takes ages.
This is my search code (don't mind the Dutch parts):
public IQueryable<Item> GetItems(string vZoekString)
{
db.Configuration.LazyLoadingEnabled = false;
//Split de ZoekString
var searchTerms = vZoekString.ToLower().Split(null);
// TODO: alles in een db query
//Resultaten oplijsten
var term = searchTerms[0];
var results = db.item_general.Where(c => c.ITEM.Contains(term) || c.DSCA.Contains(term));
//Zoeken op alle zoektermen
if (searchTerms.Length > 1)
{
for (int i = 0; i < searchTerms.Length; i++)
{
var tempTerm = searchTerms[i];
results = results.Where(c => c.ITEM.Contains(tempTerm) || c.DSCA.Contains(tempTerm));
}
}
//Show
return results;
And then, these results are returned to the view like this:
public ActionResult SearchResults(string vZoekString, string filterValue, int? pageNo)
{
//Bewaking zoekstring
if (vZoekString != null)
{
pageNo = 1;
}
else
{
vZoekString = filterValue;
}
//De zoekstring doorgeven
if (vZoekString == null)
{
return RedirectToAction("Index", "Home");
}
else
{
ViewBag.ZoekString = vZoekString;
}
//Ophalen Items via Business-Object
//var vItems = new SearchItems().GetItems(vZoekString);
SearchItems vSearchItems = new SearchItems();
IQueryable<Item> vItems = vSearchItems.GetItems(vZoekString);
//Nummering
int pageSize = 10;
int page = (pageNo ?? 1);
//Show
return View(vItems.OrderBy(x => x.ITEM).AsNoTracking().ToPagedList(page, pageSize));
}
What can be wrong in my situation? Am I overlooking something?
UPDATE:
I've checked my code, and it seems that everything works very quickly, but it takes more than 10 seconds when it reaches .ToPagedList(). So my guess is that there is something wrong with that. I'm using the paged list from Nuget.
While I can't evaluate your view code without seeing it the problem could very well be in the database query.
An IQueryable does not actually load anything from the database until you use the results. So the database query will only be run after the View code has started.
Try changing the View call to:
var items = vItems.OrderBy(x => x.ITEM).AsNoTracking().ToPagedList(page, pageSize);
return View(items);
And then check to see if the View is still the bottleneck.
(This should probably be a comment instead, but I don't have the reputation....)
In most cases where you face performance issues with MVC and EF it is due to returning entities to views and getting stung by lazy loading. The reason for this is that when ASP.Net is told to send an object to the browser it needs to serialize it. The process of serialization iterates over the entity which touches lazy-load proxies, triggering those related entities to load one at a time.
You can detect this by running a profiler against your database, set a breakpoint point prior to the end of your action, then watch what queries execute as the action call returns. Lazy loading due to serialization will appear as a number of individual (TOP 1) queries being executed in rapid succession after the action has completed prior to the page rendering.
The simplest suggestion to avoid this pain is don't return entities from controllers.
IQueryable<Item> vItems = vSearchItems.GetItems(vZoekString);
var viewModels = vItems.OrderBy(x => x.ITEM)
.Select(x => new ItemViewModel
{
ItemId = x.ItemId,
// .. Continue populating view model. If model contains a hierarchy of data, map those to related view models as well.
}).ToPagedList(page, pageSize);
return View(viewModels);
The benefit of this approach:
The .Select() will result in a query that only retrieves the data you actually need to populate the view models. (The data your view needs) Faster query, less data across the wire between DB server -> App server -> Browser
This doesn't result in any lazy loads.
The caveat of this approach:
You need to take care that the .Select() expression will go to SQL, so no .Net or private functions for stuff like translating/formatting data. Populate raw values into the view model then expose properties on the view model to do the translation which will serialize that formatted data to the client. Basic stuff like FullName = x.FirstName + " " + x.LastName is fine, but avoid things like OrderDate = DateTime.Parse(x.DateAsISO) if the DB stored dates as strings for example.
You can leverage mappers like Automapper to assist with mapping between Entity and ViewModel. Provided the mapping tool inspects/traverses the destination to populate rather than the source, you should be good. Automapper does support integration within IQueryable so it would be a worthwhile investment to research that if you want to leverage a mapper.
I know it's not something unusual to make such kind of queries but I think I get lost so I seek help. I have to tables with relation 1:N and to make it more clear I'll post a print screen from the management studio :
I am working on a asp.net mvc 3 project and I need to make a view where all Documents will be shown (and some filter and stuff, but I think that is irrelevant for this case). I need the data from the table Documents and only one specific record for each document from the DocumentFields table. This record is the record holding the name of the Document and it's uniqueness is DocumentID == Docmuents.Id, DocumentFields.RowNo == 1 and DocumentsFields.ColumnNo == 2. This is unique record for every Document and I need to get the FieldValue from this record which actually holds the Name of the Document.
I am not very sure how to build my query (maybe using JOIN) and I also would like to make my view strongly typed passing a model of type Documents but I'm not sure if it's possible, but I think depending on the way the query is build will determine the type of the model for the view.
I believe what you want is something like this:
var results =
from d in dbContext.Documents
join df in dbContext.DocumentFields
on new { d.Id, RowNo = 1, ColumnNo = 2 } equals
new { Id = df.DocumentId, df.RowNo, df.ColumnNo }
select new
{
Document = d,
DocumentName = df.FieldValue
};
Of course if you set up navigation properties, you can just do this:
var results =
from d in dbContext.Documents
let df = d.DocumentFields.First(x => x.RowNo == 1 && x.ColumnNo == 2)
select new
{
Document = d,
DocumentName = df.FieldValue
};
I´m having a problem, I retrieve all the Loans I have stored in my database like this:
list_loans = db.Loan.Where(x => x.State.id_state != 6).ToList();
db is the Object context.
Then, I assign that list as the DataSource for my DataGridView.
dgv_Loans.Datasource = list_loans;
With that info, I add some columns. Like for example, installments left to pay. I get that value by counting the result of a query.
The user can order the result using some options. Is easy to order the result from the fields that the entity have (using linq), but I dont know how to order the results using this new columns.
I read some posts here and tried this:
dgv_Loans.Sort(dgv_Loans.Columns["installments_left"], ListSortDirection.Ascending);
By doing this, I´m getting the following exception at runtime:
"DataGridView control must be bound to an IBindingList object to be sorted."
Is there anyway to use linq to orderby created columns in a DataGridViewColumn? Or how can I solve this error?
I know there are related posts, but after reading them, I can´t find a solution to this specific problem. Thats why I showed how I implemented to get some advice..
Rather than binding directly to the list retrieved from database, what I generally do is have a view class and have all the calculated properties in that class
public class LoanView : Loan {
public LoanView(Loan loan){
}
public int InsallmentsLeft { get { return ...; } }
}
and then bind the datasource to a list of this, this keeps sorting working.
Concerning about Sort datagridview by created columns using Entity Framework
I guess you need this Presenting the SortableBindingList<T>
Usage:
loanBindingSource.DataSource = new SortableBindingList<Loan>(list_loans.ToList());
dgv_Loans.Datasource = loanBindingSource;
int ID = Convert.ToInt32(cmbDepartments.SelectedValue);
var EmployeeList = from Employee in db.Employee
where Employee.DepartmentID == ID
select new
{
Employee.FirstName,
Employee.LastName
};
dataGridView1.DataSource = EmployeeList.ToList();
You could directly give the data source to dataGridView1.DataSource but you must write ToList() at the end of your query:
int ID = Convert.ToInt32(cmbDepartmanlar.SelectedValue);
dataGridView1.DataSource = (from Employee in db.Employee
where Employee.DepartmentID == ID
select new
{
Employee.FirstName,
Employee.LastName
}).ToList();
with my Repository classes, I use LinqToSql to retrieve the data from the repository (eg. Sql Server 2008, in my example). I place the result data into a POCO object. Works great :)
Now, if my POCO object has a child property, (which is another POCO object or an IList), i'm trying to figure out a way to populate that data. I'm just not too sure how to do this.
Here's some sample code i have. Please note the last property I'm setting. It compiles, but it's not 'right'. It's not the POCO object instance .. and i'm not sure how to code that last line.
public IQueryable<GameFile> GetGameFiles(bool includeUserIdAccess)
{
return (from q in Database.Files
select new Core.GameFile
{
CheckedOn = q.CheckedOn.Value,
FileName = q.FileName,
GameFileId = q.FileId,
GameType = (Core.GameType)q.GameTypeId,
IsActive = q.IsActive,
LastFilePosition = q.LastFilePosition.Value,
UniqueName = q.UniqueName,
UpdatedOn = q.UpdatedOn.Value,
// Now any children....
// NOTE: I wish to create a POCO object
// that has an int UserId _and_ a string Name.
UserAccess = includeUserIdAccess ?
q.FileUserAccesses.Select(x => x.UserId).ToList() : null
});
}
Notes:
Database.Files => The File table.
Database.FilesUserAccess => the FilesUserAccess table .. which users have access to the GameFiles / Files table.
Update
I've now got a suggestion to extract the children results into their respective POCO classes, but this is what the Visual Studio Debugger is saying the class is :-
Why is it a System.Data.Linq.SqlClient.Implementation.ObjectMaterializer<..>
.Convert<Core.GameFile> and not a List<Core.GameFile> containing the POCO's?
Any suggestions what that is / what I've done wrong?
Update 2:
this is what i've done to extract the children data into their respective poco's..
// Now any children....
UserIdAccess = includeUserIdAccess ?
(from x in q.FileUserAccesses
select x.UserId).ToList() : null,
LogEntries = includeUserIdAccess ?
(from x in q.LogEntries
select new Core.LogEntry
{
ClientGuid = x.ClientGuid,
ClientIpAndPort = x.ClientIpAndPort,
// ... snip other properties
Violation = x.Violation
}).ToList() : null
I think that all you need to do is to put another Linq query in here:
q.FileUserAccesses.Select(x => x.UserId).ToList()
i.e. You want to select data from the FileUserAccess records - which I'm assuming are Linq to SQL classes, so to do this you can have something like:
(from fua in q.FileUserAccesses
select new PocoType
{
UserID = fua.UserID,
Name = fua.User.UserName // Not sure at this point where the name comes from
}).ToList()
That should get you pointed in the right direction at least.
What is the type of UserIdAccess? How is it not 'right'? Are you getting the 'wrong' data? if so have you checked your database directly to make sure the 'right' data is there?