Does Entity framework check for redundant OrderBy() methods - asp.net-mvc

I have the following Repository model method:
public IQueryable<AccountDefinition> FindAccountDefinition(string q)
{
return entities.AccountDefinitions.Include(a => a.SDOrganization)
.Where (a=> String.IsNullOrEmpty(q) ||
a.ORG_NAME.ToUpper().StartsWith(q.ToUpper()))
.OrderBy(a=>a.ORG_NAME);
}
And the following action method:
[OutputCache(CacheProfile = "short", Location = OutputCacheLocation.Client,VaryByHeader="X-Requested-With", VaryByParam = "*")]
public ActionResult Index(string searchTerm=null, int page = 1)
{
//code goes here
var accountdefinition = repository.FindAccountDefinition(searchTerm== null ? null : searchTerm.Trim())
.OrderBy(a => a.ORG_NAME)
.ToPagedList(page, pagesize);
if (Request.IsAjaxRequest())
{
ViewBag.FromSearch = true;
return PartialView("_CustomerTable",accountdefinition);
}
return View(accountdefinition);
}
Currently I am doing the OrderBy(a => a.ORG_NAME) both inside the action method & inside the repository method. so how will entity framework and linq deal with this. I need to keep the OrderBy inside the repository method, since many other action methods are calling this repository method. and inside the action method I can not apply the ToPagedList unless I specify an OrderB. so my questions are:-
how will EF & linq deal with the duplicate OrberBy?
and will the OrderBy be done the DB level or on the server ?
Thanks
Edit
here is the generated sql statement from the sql profiler , and there is two OrderBy commands:-
exec sp_executesql N'SELECT TOP (15)
[Project1].[C1] AS [C1],
[Project1].[ORG_ID] AS [ORG_ID],
[Project1].[LOG_LOGO] AS [LOG_LOGO],
[Project1].[HEAD_LOGO] AS [HEAD_LOGO],
[Project1].[ORG_NAME] AS [ORG_NAME],
[Project1].[HASATTACHMENT] AS [HASATTACHMENT],
[Project1].[LOGIN_URI] AS [LOGIN_URI],
[Project1].[SUPPORT_EMAIL] AS [SUPPORT_EMAIL],
[Project1].[DEFAULTSITEID] AS [DEFAULTSITEID],
[Project1].[ORG_ID1] AS [ORG_ID1],
[Project1].[NAME] AS [NAME],
[Project1].[CREATEDTIME] AS [CREATEDTIME],
[Project1].[DESCRIPTION] AS [DESCRIPTION]
FROM ( SELECT [Project1].[ORG_ID] AS [ORG_ID], [Project1].[LOG_LOGO] AS [LOG_LOGO], [Project1].[HEAD_LOGO] AS [HEAD_LOGO], [Project1].[ORG_NAME] AS [ORG_NAME], [Project1].[HASATTACHMENT] AS [HASATTACHMENT], [Project1].[LOGIN_URI] AS [LOGIN_URI], [Project1].[SUPPORT_EMAIL] AS [SUPPORT_EMAIL], [Project1].[DEFAULTSITEID] AS [DEFAULTSITEID], [Project1].[ORG_ID1] AS [ORG_ID1], [Project1].[NAME] AS [NAME], [Project1].[CREATEDTIME] AS [CREATEDTIME], [Project1].[DESCRIPTION] AS [DESCRIPTION], [Project1].[C1] AS [C1], row_number() OVER (ORDER BY [Project1].[ORG_NAME] ASC) AS [row_number]
FROM ( SELECT
[Extent1].[ORG_ID] AS [ORG_ID],
[Extent1].[LOG_LOGO] AS [LOG_LOGO],
[Extent1].[HEAD_LOGO] AS [HEAD_LOGO],
[Extent1].[ORG_NAME] AS [ORG_NAME],
[Extent1].[HASATTACHMENT] AS [HASATTACHMENT],
[Extent1].[LOGIN_URI] AS [LOGIN_URI],
[Extent1].[SUPPORT_EMAIL] AS [SUPPORT_EMAIL],
[Extent1].[DEFAULTSITEID] AS [DEFAULTSITEID],
[Extent2].[ORG_ID] AS [ORG_ID1],
[Extent2].[NAME] AS [NAME],
[Extent2].[CREATEDTIME] AS [CREATEDTIME],
[Extent2].[DESCRIPTION] AS [DESCRIPTION],
1 AS [C1]
FROM [dbo].[AccountDefinition] AS [Extent1]
INNER JOIN [dbo].[SDOrganization] AS [Extent2] ON [Extent1].[ORG_ID] = [Extent2].[ORG_ID]
WHERE (#p__linq__0 IS NULL) OR (( CAST(LEN(#p__linq__0) AS int)) = 0) OR (( CAST(CHARINDEX(UPPER(#p__linq__1), UPPER([Extent1].[ORG_NAME])) AS int)) = 1)
) AS [Project1]
) AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[ORG_NAME] ASC',N'#p__linq__0 nvarchar(4000),#p__linq__1 nvarchar(4000)',#p__linq__0=NULL,#p__linq__1=NULL

1) how will EF & linq deal with the duplicate OrberBy?
It will ignore the second OrderBy because the correct way to specify multiple ORDER BY clauses is to use OrderBy first and then use ThenBy
2) and will the OrderBy be done the DB level or on the server ?
At the DB level since you are applying it to the IQueryable<T> before calling ToPagedList which will execute the statement.

I suppose, you have to call OrderBy in your controller, because the ToPagedList method requires IOrderedQueryable instead of IQueryable as the parameter, so what about returning IOrderedQueryable<AccountDefinition> from your repository?
public IOrderedQueryable<AccountDefinition> FindAccountDefinition(string q) {
return entities.AccountDefinitions
.Include(a => a.SDOrganization)
.Where (a => String.IsNullOrEmpty(q) ||
a.ORG_NAME.ToUpper().StartsWith(q.ToUpper()))
.OrderBy(a=>a.ORG_NAME);
}
OrderBy returns IOrderQueryable by default, so there is no overhead and the other methods shouldn't be affected by this change.

Related

Joining the Resource Table and the Resources_Role Table in a Repository using Linq query

How do I join the Resources Table to the Resources_role table where resources_id column is common in both table. Below is my code to fetch the Resources table using the Role_id, however I want all the records from the resources table that share the same resources_id column with the Resources_Role to be fetched.
How do I do this join please, below is my code
public IQueryable <Resources_Role> FindResourceByRoleID(int RoleId)
{
var roleid = from p in context.Resources_Role
select p;
roleid = roleid.Where(p => p.ROLE_ID==(RoleId));
return roleid;
}
The thing you should watch is the method return type. Depending on what you want to return, you should adjust the return type based on result set from JOIN query you're using:
// returning resources
public IQueryable<Resources> FindResourceByRoleID(int RoleId)
{
return (from res in context.Resources
join role in context.Resources_Role
on res.resources_id equals role.resources_id
where res.ROLE_ID == RoleId
select res);
}
// returning resource role
public IQueryable<Resources_Role> FindResourceRoleByRoleID(int RoleId)
{
return (from role in context.Resources_Role
join res in context.Resources
on role.resources_id equals res.resources_id
where res.ROLE_ID == RoleId
select role);
}
Those queries above will produce SQL statements as shown below:
-- returns resources
SELECT t0.* FROM Resources AS t0 INNER JOIN Resources_Role AS t1
ON t0.resources_id = t1.resources_id
WHERE t0.ROLE_ID = #RoleId
-- returns resources role
SELECT t0.* FROM Resources_Role AS t0 INNER JOIN Resources AS t1
ON t0.resources_id = t1.resources_id
WHERE t1.ROLE_ID = #RoleId
Note: Cannot implicitly convert type System.Linq.IQueryable<Resource> to System.Linq.IQueryable<Resources_Role> is self-explanatory: it occurs because your query returns IQueryable<Resource> but the method return type set to IQueryable<Resources_Role>. You must adjust the query to return IQueryable<Resources_Role> instead.
After you've got your roleId from your method, you could do something like the following to return a Resource:
var resources = from resource in context.Resources
where resource.ROLE_ID == roleId
join resource_role in context.Resources_Role
on resource.resources_id equals resource_role.resource_id
select resource;
To get a Resource_Role you could do something like this:
var resource_role = from role in context.Resource_Roles
join resource in context.Resources_Role.Where(resource_role => resource_role.ROLE_ID == roleId)
on role.resources_id equals resource.resource_id
select role;
I'm afraid I don't have a working MVC env to test any of this in atm, but it should be a good starting point.

On a four-week struggle for a functional LINQ LAMBDA expression

I have been trying to find the correct LINQ LAMBDA expression for the last month, with zero successful results.
I cannot use the following tools:
Linqer is unable to run because the Microsoft tool it uses to create the SQL connection (the dbml file) refuses to install on my Win8.1 system
LinqPad doesn’t provide an actual translation until you actually buy the product (which makes the “trial” fundamentally broken in the first place)
I have three levels of tables that I need to bring back into a single viewmodel.
Level one is a company table. Easy as s**t.
Level two is a "cycle" table. This is where I have gotten hung up on, since many cycles can exist for a company, but I need to grab only the latest cycle by date.
Level three is a pair of tables that exist off the cycle, I only need a true/false test for content in those tables for the cycle in question. I haven't even tried this yet.
So far I have come up with a minimally functional SQL script (that only deals with the first two levels), but my MVC project is making use of several tools that hook straight into LAMBDA expressions, including PagedList. I need a LAMBDA expression and not a pure SQL expression.
My SQL:
SELECT
co.CompanyId
, co.CompanyName
, co.CompanyCity
, co.NumberOfEmployees
, co.ProspectingScore
, cd.PDFResourceLibrary
, cd.PresentationDone
, cd.MOUDone
FROM Company AS co
OUTER APPLY (
SELECT
TOP 1 MAX(CycleDate) AS CycleDate
, PDFResourceLibrary
, PresentationDone
, MOUDone
FROM Cycle AS cy
WHERE cy.CompanyId = co.CompanyId
GROUP BY PDFResourceLibrary, PresentationDone, MOUDone
) AS cd
ORDER BY co.ProspectingScore DESC
I have tried a number of lambda expressions to date:
db.Company
.GroupJoin(
db.Cycle
, co => co.CompanyId
, cy => cy.CycleId
, (x, y) => new { Company = x, Cycle = y }
).Select(
y => y.Cycle.OrderByDescending(y => y.CycleDate).SingleOrDefault()
).ToList();
But this throws a local variable cannot have the same name as a method type parameter as well as a Cannot implicitly convert type System.Collections.Generic.List to System.Collections.Generic.IEnumerable error.
Converting it to a Join flags the OrderByDescending as invalid, but I need that to drop all but the most latest cycle.
I also can't seem to do a join to save my effin' life. All the examples out there fail with my system.
For example, a join that comes sooooooo close is:
db.Company.Join(
db.Cycle.OrderByDescending(x => x.CycleDate).SingleOrDefault()
, co => co.CompanyId
, cy => cy.CycleId
, (x, y) => new { Company = x, Cycle = y }
).ToList();
but then it claims The type arguments for method Queryable Join cannot be inferred from the usage. Like, --what??
I have also tried the following:
db.Company.Include(
x => x.Cycle.OrderByDescending(y => y.CycleDate).SingleOrDefault()
).ToList();
which works for the company but I cannot seem to drill past the company and into the cycle, when I go, #(item.Cycle.PresentationDone it says that ICollection<Cycle> does not contain a definition for PresentationDone, even though I have an ICollection for Cycle in my Company model. It's right there, but the system won’t see it to follow.
An attempt with
db.Company.Select(x => new { Company = x, Cycle = x.Cycle.OrderByDescending(y => y.CycleDate).Single() }).ToList();
also throws the List to IEnumerable conversion error.
As a final note, please keep in mind that I am bringing two models into the same page, and the second model is the same as the first but focuses only on the company. IT works. It has no problem pulling data out of the DB:
viewModel.AllCompanies = db.Company.ToList();
Because it does not need to pull anything from the cycle -- I am ignoring anything beneath the Company level. But for the first query, I have to bring several items off of the cycle, and so I need to query for the most recent cycle.
EDIT:
With the generous assistance of Ivan Stoev I have assembled the following:
var query = (IPagedList<DashboardUserData>)(
from co in db.Company
join cy in db.Cycle on co.CompanyId equals cy.CycleId into cycles
from cd in cycles.OrderByDescending(cy => cy.CycleDate).Take(1).DefaultIfEmpty()
orderby co.ProspectingScore descending
select new {
CompanyId = co.CompanyId,
CompanyName = co.CompanyName,
CompanyCity = co.CompanyCity,
NumberOfEmployees = co.NumberOfEmployees,
ProspectingScore = co.ProspectingScore,
PDFResourceLibrary = (bool?)cd.PDFResourceLibrary,
PresentationDone = (bool?)cd.PresentationDone,
MOUDone = (bool?)cd.MOUDone
}).ToPagedList(regionPageIndex, pageSize);
And my model is such:
public IPagedList<DashboardUserData> RegionalCompanies { get; set; }
public class DashboardUserData {
public Guid CompanyId { get; set; }
public string CompanyName { get; set; }
public string CompanyCity { get; set; }
public int? NumberOfEmployees { get; set; }
public int? ProspectingScore { get; set; }
public bool? PDFResourceLibrary { get; set; }
public bool? PresentationDone { get; set; }
public bool? MOUDone { get; set; }
}
But for some reason I am unable to attach the data to the model. I get the following error:
Unable to cast object of type 'PagedList.PagedList`1[<>f__AnonymousType9`8[System.Guid,System.String,System.String,System.Nullable`1[System.Int32],System.Nullable`1[System.Int32],System.Nullable`1[System.Boolean],System.Nullable`1[System.Boolean],System.Nullable`1[System.Boolean]]]' to type 'PagedList.IPagedList`1[CCS.Models.DashboardUserData]'.
It's probably something stupidly simple, but I'm missing it.
EDIT 2:
When I add a filter to the original table, Company:
var query = (IPagedList<DashboardUserData>)(
from co in db.Company
where co.RegionId == new Guid(User.GetClaimValue("Region"))
I now get an issue of:
Only parameterless constructors and initializers are supported in LINQ to Entities
Still have that model issue from the first Edit, tho.
Edit 3:
Huh, I may have solved Edit 1:
viewModel.RegionalCompanies = (
from co in db.Company
where co.RegionId == regionId
join cy in db.Cycle on co.CompanyId equals cy.CycleId into cycles
from cd in cycles.OrderByDescending(cy => cy.CycleDate).Take(1).DefaultIfEmpty()
orderby co.ProspectingScore descending
select new DashboardUserData {
CompanyId = co.CompanyId,
CompanyName = co.CompanyName,
CompanyCity = co.CompanyCity,
NumberOfEmployees = co.NumberOfEmployees,
ProspectingScore = co.ProspectingScore,
PDFResourceLibrary = cd.PDFResourceLibrary,
PresentationDone = cd.PresentationDone,
MOUDone = cd.MOUDone
}).ToPagedList(regionPageIndex, pageSize);
But now the second viewModel:
viewModel.AllOtherCompanies = await db.Company.Where(c => c.RegionId != regionId).Include(c => c.Province).ToPagedListAsync(allPageIndex, pageSize);
return View(viewModel);
Is throwing one very confusing error message:
The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.
which I have never seen before, even with the original code. Googling right now.
Edit 4:
OMFG, I think I have it working. I was concentrating on getting the first model to work properly with IPagedList, and forgot that IPagedList requires an order in order to page properly. This is coming once I get column sorting and paging implemented on the page side and the correct code in the controller, but once I stuck in a temporary .OrderBy() in the second viewModel, everything suddenly stood up properly.
A big shout-out to Ivan Stoev, your reply was a massive kick in the right direction!! Thank you!!
I would suggest you when working with complex queries, to use the LINQ query syntax for the most parts of the query because it's much easier to follow and modify due to the transparent identifiers. Also it maps more natively to the SQL query.
For instance, here is the LINQ equivalent of your SQL query:
var query =
from co in db.Company
from cd in (
from cy in db.Cycle
where cy.CompanyId == co.CompanyId
group cy by new { cy.PDFResourceLibrary, cy.PresentationDone, cy.MOUDone } into g
select new
{
CycleDate = g.Max(cy => cy.CycleDate),
g.Key.PDFResourceLibrary,
g.Key.PresentationDone,
g.Key.MOUDone
}
)
.OrderByDescending(cy => cy.CycleDate).Take(1) // TOP 1
.DefaultIfEmpty() // OUTER
orderby co.ProspectingScore descending
select new
{
co.CompanyId,
co.CompanyName,
co.CompanyCity,
co.NumberOfEmployees,
co.ProspectingScore,
cd.PDFResourceLibrary,
cd.PresentationDone,
cd.MOUDone
};
EF generated SQL query from the above:
SELECT
[Extent1].[CompanyId] AS [CompanyId],
[Extent1].[CompanyName] AS [CompanyName],
[Extent1].[CompanyCity] AS [CompanyCity],
[Extent1].[NumberOfEmployees] AS [NumberOfEmployees],
[Extent1].[ProspectingScore] AS [ProspectingScore],
[Limit1].[PDFResourceLibrary] AS [PDFResourceLibrary],
[Limit1].[PresentationDone] AS [PresentationDone],
[Limit1].[MOUDone] AS [MOUDone]
FROM [dbo].[Company] AS [Extent1]
OUTER APPLY (SELECT TOP (1) [Project1].[PDFResourceLibrary] AS [PDFResourceLibrary], [Project1].[PresentationDone] AS [PresentationDone], [Project1].[MOUDone] AS [MOUDone]
FROM ( SELECT
[GroupBy1].[A1] AS [C1],
[GroupBy1].[K1] AS [PDFResourceLibrary],
[GroupBy1].[K2] AS [PresentationDone],
[GroupBy1].[K3] AS [MOUDone]
FROM ( SELECT
[Extent2].[PDFResourceLibrary] AS [K1],
[Extent2].[PresentationDone] AS [K2],
[Extent2].[MOUDone] AS [K3],
MAX([Extent2].[CycleDate]) AS [A1]
FROM [dbo].[Cycle] AS [Extent2]
WHERE [Extent2].[CompanyId] = [Extent1].[CompanyId]
GROUP BY [Extent2].[PDFResourceLibrary], [Extent2].[PresentationDone], [Extent2].[MOUDone]
) AS [GroupBy1]
) AS [Project1]
ORDER BY [Project1].[C1] DESC ) AS [Limit1]
ORDER BY [Extent1].[ProspectingScore] DESC
This should cover the question of how to convert the original SQL query.
But do you really need to follow the original SQL query? According to this requirement:
many cycles can exist for a company, but I need to grab only the latest cycle by date
it looks more natural to use something like this:
var query =
from co in db.Company
join cy in db.Cycle on co.CompanyId equals cy.CycleId into cycles
from cd in cycles.OrderByDescending(cy => cy.CycleDate).Take(1).DefaultIfEmpty()
orderby co.ProspectingScore descending
select new
{
co.CompanyId,
co.CompanyName,
co.CompanyCity,
co.NumberOfEmployees,
co.ProspectingScore,
cd.PDFResourceLibrary,
cd.PresentationDone,
cd.MOUDone
};
which generates:
SELECT
[Extent1].[CompanyId] AS [CompanyId],
[Extent1].[CompanyName] AS [CompanyName],
[Extent1].[CompanyCity] AS [CompanyCity],
[Extent1].[NumberOfEmployees] AS [NumberOfEmployees],
[Extent1].[ProspectingScore] AS [ProspectingScore],
[Limit1].[PDFResourceLibrary] AS [PDFResourceLibrary],
[Limit1].[PresentationDone] AS [PresentationDone],
[Limit1].[MOUDone] AS [MOUDone]
FROM [dbo].[Company] AS [Extent1]
OUTER APPLY (SELECT TOP (1) [Project1].[PDFResourceLibrary] AS [PDFResourceLibrary], [Project1].[PresentationDone] AS [PresentationDone], [Project1].[MOUDone] AS [MOUDone]
FROM ( SELECT
[Extent2].[CycleDate] AS [CycleDate],
[Extent2].[PDFResourceLibrary] AS [PDFResourceLibrary],
[Extent2].[PresentationDone] AS [PresentationDone],
[Extent2].[MOUDone] AS [MOUDone]
FROM [dbo].[Cycle] AS [Extent2]
WHERE [Extent1].[CompanyId] = [Extent2].[CycleId]
) AS [Project1]
ORDER BY [Project1].[CycleDate] DESC ) AS [Limit1]
ORDER BY [Extent1].[ProspectingScore] DESC

How can I translate this linq query to a breeze query

I am in the process of learning Durandal and Breeze. And have choosing to create a SPA version of nerddinner.
The first query I need to execute is this:
public IEnumerable<JsonDinner> GetMostPopularDinners(int limit = 10)
{
var mostPopularDinners = from dinner in _db.Context.Dinners.Include("RSVPs")
where dinner.EventDate >= DateTime.Now
orderby dinner.RSVPs.Count descending
select dinner;
if (limit > 50 || limit <= 0)
limit = 10;
return mostPopularDinners.Take(limit).AsEnumerable().Select(JsonDinnerFromDinner);
}
I have started to write it with breeze but I am having trouble with this line " orderby dinner.RSVPs.Count descending" this is what I have so far.
var getMostPopularDinners = function() {
var query = EntityQuery
.from('dinners')
.where('eventDate', '>=', new Date(Date.now()))
.orderByDesc('RSVPs')
.expand('RSVPs');
Sorry, Breeze doesn't yet support ordering or filtering on an aggregate value ('count' in this case).
What you can do is turn this into a named query. (which is not well documented...) Basically this involves using the EntityQuery.withParameters method to pass additional parameters to any service method. So you can construct a query like the following that both passes parameters and still uses Breeze's IQueryable support.
EntityQuery.from("GetMostPopularDinners")
.withParameters({ EventDate: new Date(Date(now()) })
.take(10);
where your controller method would look something like this:
[HttpGet]
public IQueryable<Dinner> GetMostPopularDinners(DateTime eventDate) {
return _db.Context.Dinners.
.Where(dinner => dinner.EventDate >= eventDate)
.OrderByDescending(dinner => dinner.RSVPs.Count);
}
and ... you should not need to do an JsonDinnerFromDinner" call; Breeze handles this automatically.

How do I use 2 include statements in a single MVC EF query?

I am trying to write a query that includes 2 joins.
1 StoryTemplate can have multiple Stories
1 Story can have multiple StoryDrafts
I am starting the query on the StoryDrafts object because that is where it's linked to the UserId.
I don't have a reference from the StoryDrafts object directly to the StoryTemplates object. How would I build this query properly?
public JsonResult Index(int userId)
{
return Json(
db.StoryDrafts
.Include("Story")
.Include("StoryTemplate")
.Where(d => d.UserId == userId)
,JsonRequestBehavior.AllowGet);
}
Thank you for any help.
Try to flatten your hierarchy if it works for you. Here is a sample, and you may want to customize it for your needs.
var result = from c in db.Customers
join o in db.Orders
on c equals o.Customers
select new
{
custid = c.CustomerID,
cname = c.CompanyName,
address = c.Address,
orderid = o.OrderID,
freight = o.Freight,
orderdate = o.OrderDate
};
If flattering does not meet your requirements then you need to use query that returns a Nested Group. Finally, look at the following link for more references - LINQ Query Expressions .

Is this Where condition in Linq-to-sql join correct?

I have the following Iqueryable method to show details of a singl material,
public IQueryable<Materials> GetMaterial(int id)
{
return from m in db.Materials
join Mt in db.MeasurementTypes on m.MeasurementTypeId equals Mt.Id
where m.Mat_id equals id
select new Materials()
{
Id = Convert.ToInt64(m.Mat_id),
Mat_Name = m.Mat_Name,
Mes_Name = Mt.Name,
};
}
Any suggestion....
Your where clause should be a boolean expression, like this:
where m.Mat_id == id
You use the keyword equals for joins, but a where clause should use a boolean expression, think of it this way: whatever is in where something it should work as if it were in a if(something).

Resources