ASP.NET MVC View Model with LINQ To Entities - c#

Let's say I create a query result
var query = from a in tblXYZ join c in tblABC on a.id = b.id select new {a.x, b.x};
What's the best way to pass that into a view? Should I create a new object and copy the query result into it?

I think it's almost always the preferred mechanism to create a view-specific model. I would also second #Marc's recommendation to materialize the query in the controller and pass the view to the list. If you have an issue with a query it's much easier to diagnose if the query is executed in the controller rather than the view. The stack trace is actually useful at that point.

As presented, that will be an anonymous type, which can be accessed (even easier in 4.0 via dynamic) but it is ugly to do so. It also currently suffers from ambiguity over when the data access happens, as the LINQ is deferred, meaning that if you pass that query into the view, you are really doing you data access during the view (and not inside the controller).
I would be tempted to create a class to represent the data (essentially a view-model), and return a list of the entities (not a deferred query).

Put the query in repository. In controller pass the query result to the specified model-view for that view.
Model-View - a specified class for transferring data from controller to the view.

Related

oData EndPoint: Does it affect the SQL query?

I’ve been using OData for my apis.
While I generally like what is has to offer, it only uses the data Post my query, which forces me to to construct all the relationships ahead of time.
Does an oData EndPoint with EntityFramework pass my oData parameters to be execute pre my SQL query?
Right now if I plan to possibly use oData Syntaxes like $Expand, I have to use EF Include ahead of time. Once again, the issue being that EF must build all of the potential relationships that I may use $Expand with...even if I don’t $expand anything.
Another example is if I am to use the $top(100) syntax. Say I had 10000 results, EF will download all 10000 from the DB, and then OData will select the Top 100.
Would an oData endpoint inject itself between EF and the DB and only select 100 results from the DB in this case?
In general OData and EF go hand in hand, OData translates the incoming HTTP request into a Linq-to-Entities expression that EF then translates to a SQL expression.
tl;dr
All of your comments and observations point to incorrect implementation within your controller, it sounds suspiciously as if you have followed a Repository Pattern based example instead of an EF based examples.
Does an oData EndPoint with EntityFramework pass my oData parameters to be execute pre my SQL query?
That is exactly what the OData framework was designed to enable, but there is 1 caveat, you must configure your controllers in a way that the parameters can be passed through.
There are two mechanisms that allow this to happen, the first is that your controller needs to return an IQueryable<T> result (or you must pass an IQueryable<T> to one of the negotiated response handlers). The other is that you must not apply your own filter expressions that might contradict the parameters, otherwise you may result in no records being returned.
The following is an example of the two standard Get endpoints on an OData controller that will return a Vehicle query that will allow the $expand,$select and $filter expressions to be passed through:
[ODataRoute]
[HttpGet]
[OData.EnableQuery]
public IHttpActionResult Get(ODataQueryOptions<Vehicle> queryOptions)
{
return Ok(db.Vehicles);
}
[ODataRoute]
[HttpGet]
[OData.EnableQuery]
public IHttpActionResult Get([FromOdataUri] int key, ODataQueryOptions<Vehicle> queryOptions)
{
return SingleResultAction(db.Vehicles.Where(x => x.Id == key));
}
If I call this with the following query options:
$filter=Make eq Holden and Model eq Commodore&$orderby=Year desc
then that will translate into a SQL query similar to this:
(SELECT * would be expanded in full)
DECLARE #make varchar(20) = 'Holden';
DECLARE #model varchar(20) = 'Commodore';
SELECT *
FROM Vehicle
WHERE Make = #make
AND Model = #model
ORDER BY Year DESC
Yes the parameters will be properly parameterised as well!
This simple controller will also pass through the $expand request and automatically join the necessary tables, there is no need for us to know or think about the potential includes up front at all.
In fact, if I add $skip=5 and $top=2 those clauses will also be applied directly to the SQL statement, and at most only 2 rows will be returned from the database.
There are two common scenarios where all this auto-magical query translation and pass through mumbo jumbo gets broken.
In the controller method, (for either of the collection or item) you do not return an IQueryable result, or you have otherwise executed the query and resolved it to an IEnumerable and then you have returned that, perhaps by casting it back to an IQueryable.
This tends to happen because when we start out we tend follow simple non EF based OData examples, they usually follow a repository pattern because it is simple to explain the model, example data and implementation in a single page of code.
Public Service Announcement : Do not try to use a Repository Pattern to wrap an EF model and context, almost all the benefits from EF will be lost. EF is a Unit of work pattern, OData is designed to work with it directly.
The following controller implementation will NOT pass through the query options to the database, $top,$skip,$filter,$select,$orderby will still be applied, if it can, but only after retrieving all the records into memory first:
return Ok(db.Vehicles.ToList());
$expand will not be applied, or rather it will result in NULL related records.
The other common issue is after Actions where a data record (or records) have been processed if we want to support the entire range of query options automatically, we again need to make sure we return an IQueryable expression that queries from the DbContext. If the expression is IQueryable, but is only querying against what is already in memory, then $expand and $filter for instance can only be applied to that data which is already loaded.
SingleResultAction is a good helper method for returning an IQueryable that has been scoped toa single item, but it will still allow the full range of QueryOptions to be applied.
Welcome to a world of pain - Odata is a great idea with a pretty bad implementation. But yes, it is a amazing - doing it myself.
it only uses the data Post my query, which forces me to to construct all the
relationships ahead of time.
If this asks what I think it does (your english is VERY unclear) then no, it does not - your fault is exposing ef objects directly. I have separate api objects and expose them using AutoMapper ProjectTo. Yes, I need to define relationships ahead of time, but not on ef level.
I have to use EF Include ahead of time.
That is because you decide to. I acutally use, as I said, Automapper ProjectTo - and get the necessary expands from the OdataOptions SelectExpand information dynamically. No need to send the whole object graph to EF (which is what happens if you expand all possible expansions with includes), this will result in really bad performance. Just a page of programming to get the relevant includes.
Would an oData endpoint inject itself between EF and the DB and only select 100 results
from the DB in this case?
if it is programmed like that, yes. If someone challenged with LINQ programs it and packs ef in one of the ridiculous inefficient repository patterns that return IENumerable - then no, it will pull it possibly all. Been there seen that. But normally TOP(1) results in sql selecting only the first item.

Using a Projection in place of Find() in LINQ

Relatively new to MVC and LINQ.
Using built-in methods with LINQ/EF do not generally work for me because I' need to use multiple tables and create a projection to a new type to get the data I need.
When returning an iEnumerable, I am able to do this without a problem. My view is expecting an iEnumerable and my LINQ query provided just that.
However, when I try to replace the Find() method to return a single record, I'm running into a problem.
My projection is identical to the one that returns multiple records but I have added a where clause to limit the return to a single record (using a unique ID).
I've changed my view to expect and object of the type I'm projecting to but that is as far as I can get.
If I just return the query as is, deferred execution causes a problem in that I'm actually passing the query to my view, not the results (as expected).
So I'm trying to figure out how to (I guess) execute the query and get the object in my controller and then pass that to the view. I've tried SingleOrDefault() and the like to 'force' execution but that generates a new exception (Unable to create a constant value of type 'System.Object'. Only primitive types or enumeration types are supported in this context.)
Clearly I don't understand something that is going on here.
var model =
(from p in db.t_Protocol
join pt in db.t_ProtocolType on p.ProtocolTypeId equals pt.ProtocolTypeID
where p.ProtocolId.Equals(id)
select new ProtocolView
{
ProtocolId = p.ProtocolId,
Protocol = p.Protocol,
ProtocolType = pt.ProtocolType,
IsAdmission = p.IsAdmission,
IsReleased = p.IsReleased
})
;
My view is expecting:
#model ECM.Models.ProtocolView
First of all by stating your view is expecting #model ECM.Models.ProtocolView states that the view is only expecting a single record. If when setting your Var model the child table has more than 1 record then your projection will force n number of records into model regardless of the parent only having 1 record. for your view to expect an IEnumerable you need to change your view declaration to be #model IEnmerable<ECM.Models.ProtocolView> Also if you really need to perform a Find use IQueryable first them return the result as IEnumerable since the payload on Ienumerable is smaller than IQueryable

To list and without tolist

I would like to know when to use tolist. In the following example, both of the following do not result in error. So, which way to use?
var employees = db.Employees.Include(e => e.Department);
return View(employees);
var employees = db.Employees.Include(e => e.Department);
return View(employees.ToList());
Seems like the code is ASP.Net MVC code.. given return View(employees);
I also assume that the data is being pulled from the DB, using some LinqToSQL or EntityFramework like technology.
Given those two assumptions, I'd recommend that the latter be used. i.e. with .ToList()
Reason being, if the query is lazily evaluated, if you pass employees without .ToList(), you are essentially passing the query to the View, and it will execute when query is enumerated while rendering the View. View rendering should be fast, and not be blocked by a call to database.
.ToList() would avoid that, and force execution of the query in controller, and View will have data available in memory, for fast rendering...
Hope it answers your question.
EDIT: One caveat.. there some scenarios, for example when building APIs, like with OData APIs with WebAPI, that you actually want to return the query than the materialized list. The reason there is that by design, you want Framework to build on top of that query, before the filtered data is returned to the caller. In other words, framework does some more leg work for you, before view (serialized data - typically not HTML) is actually rendered.
After the first line is executed, employees collection will not be loaded into the memory (Lazy Loading). It is loaded when the collection is first accessed. When you call ToList() collection will be forced to be loaded into memory.
Usage is based on the trade-off between memory limitation and speed. Accessing from the memory is faster than lazy loading.

How do I sort a gridview of Linq objects based on a derived field?

I have written a page which uses Linq to query the database and bind the resulting IQueryable to a datagrid. I have a partial class which contains extra properties which derive their values based on other values brought in from the database.
Sorting works fine on fields that are actually in the database but not for the derived fields. When I attempt to sort on such a field I get an error saying "The member 'Trip.Difference' has no supported translation to SQL.
Any idea how to allow sorting on these derived fields?
The problem is that you are binding to an IQueryable, so every time you enumerate it, you are translating the LINQ expression on the IQueryable to a SQL statement and going back to the database to execute it.
If you are trying to sort on properties that are not bound to the database model then you will get the error mentioned, as those properties only exist once an object has been created from a data row.
The simplest solution is to call ToList() on the IQueryable before using it for sorting and data-binding, so that you sort on the in-memory objects where the properties are actually available. ToList() converts your IQueryable into an IEnumerable (via List<T>), and stops it from going to the database again via LINQ to SQL.
This is generally a good design pattern to follow - the last thing you want is consumers of your business layer being able to unwittingly execute arbitrary queries against your database, simply because you returned IQueryable where you should have returned IEnumerable.
Call ToEnumerable() first, and then add the OrderBy:
var q = (from a in mDataContext.Somethings()
select a).ToEnumerable().OrderBy...

How do I refactor a common LINQ subquery into a method?

I'm struggling to come up with the right words to summarize this problem, so any input on what I can add to clarify it would be appreciated.
The basic scenario is this: I have a basic CMS (with pages, users, etc.). Page is a LINQ data object, which maps directly to a Page table.
I've added a method to the Page class called GetUserPermissions. This method accepts a UserId and returns a non-LINQ class called PagePermissionSet, which describes what the user can do. PagePermissionSet is calculated via a LINQ query.
Now I want to get the list of Pages which a user has access to. The ideal implementation would be as follows:
from page in mDataContext.Pages
where page.GetUserPermissions(userId).CanView
select page
This fails, stating that there is no SQL equivalent for GetUserPermissions (which is reasonable enough), or after some refactoring of the method, that the CanView member can't be invoked on an IQueryable.
Attempt two was to add a method to the DataContext, which returns all of the permissions for each Page/User as an IQueryable:
IQueryable<PagePermissionSet> GetAllPagePermissions()
I then tried to join to this result set:
IQueryable<Page> GetAllPages(Guid? userId) {
var permissions = mDataContext.GetAllPagePermissions();
var pages =
from page in mDataContext.WikiPages
join permission in permissions on Page.FileName equals permission.PageName
where permission.CanView && permission.UserId == userId
select page;
return pages;
}
This produces the error: "The member 'WikiTome.Library.Model.PagePermissionSet.PageName' has no supported translation to SQL."
PagePermissionSet is pretty much just a shell holding data from the select clause in GetUserPermissions, and is initialized as follows:
select new PagePermissionSet(pageName, userName, canView, canEdit, canRename)
With all of that out of the way... How can I reuse the LINQ query in Page.GetUserPermissions in another query? I definitely don't want to duplicate the code, and I would prefer not to translate it to SQL for inclusion as a view at this point.
Maybe you need a compiled query?
http://msdn.microsoft.com/en-us/library/bb399335.aspx
You have a few options.
1) The quick and dirty solution is to use AsEnumerable() with your query to bring the entire Pages table down to the client side then operate on it. For small tables this should be fine, however for large tables it will be inefficient and lead to performance issues depending on the size. If you choose to use this be mindful of how it actually operates. This means updating your code to:
from page in mDataContext.Pages.AsEnumerable()
where page.GetUserPermissions(userId).CanView
select page
2) A more involved solution would be to create a stored procedure or UDF on the SQL server that you would call from the DataContext and pass parameters to. Take a look at Scott Gu's blog post: LINQ to SQL (Part 6 - Retrieving Data Using Stored Procedures).
You could then write something like:
mDataContext.GetUserPermissions(userId)
All the logic you do in your code would written in SQL and you would return the viewable pages for the given user. This bypasses the use of the PagePermissionSet properties that have no supported translation to SQL.
I was able to solve the bulk of this problem today.
The error "The member 'WikiTome.Library.Model.PagePermissionSet.PageName' has no supported translation to SQL." was caused by HOW I was initializing my PagePermissionSet objects.
I had been initializing them using a constructor, like this:
select new PagePermissionSet(pageName, userName, canView, canEdit, canRename)
However, in order for LINQ to properly track the properties, it needs to be initialized like this:
select new PagePermissionSet { PageName=pageName, UserName = userName, CanView = canView, CanEdit = canEdit, CanRename = canRename }
With this change in place, I can create a method which returns an IQueryable<PagePermissionSet>, and then join my query to that (as in the second example).

Categories

Resources