Follow up question to this:
Linq Combine Left Join Data
Say I have the following db tables:
Users
-------
UserId (PK)
UserName
Roles
-----
RoleId (PK)
RoleName
UserRoles
---------
UserId (PK)
RoleId (PK)
Users 1-M UserRoles M-1 Roles
Using LinqToSQL, I can return the following set (thanks to response from prior question):
[User1], [Role1, Role2, Role3]
[User2], [Role2, Role3]
[User3], [Role3]
The twist is I am trying to sort by Roles. How can I sort the result by Roles?
Clarification
I have a grid, when the user clicks on the Roles column, the rows would be sorted by that column.
So to start the grid would look like this:
[User1], [Role1, Role2, Role3]
[User2], [Role2, Role3]
[User3], [Role3]
Then if they sort on Roles column it would look like this
[User3], [Role3]
[User2], [Role2, Role3]
[User1], [Role1, Role2, Role3]
Just change the original answer very slightly:
from u in dataContext.Users
select new { User = u, Roles = u.UserRoles.Select(ur => ur.Role)
.OrderBy(r => r.RoleName) };
(That's assuming you want to sort each element of the result by the roles it contains. If that's not correct, please explain what you want in more detail.)
Could you not simpy use something like this?
// users is the collection you already got from Linq2Sql
var usersSorted = from u in users order by u.Roles select u;
int ascending = 1; //set to -1 for descending
from u in Users
orderby u.Roles.Count * ascending
select new { u, u.Roles.OrderBy(x => x.RoleName) }
Your query will need to cater for the many to many though (not shown).
Hey #zzz, the answers I've seen so far seem to indicate how to sort the rows for each user, whereas, if I understand your clarification, you do want that, but what you're asking for is how to then sort those statements alphabetically. I'll try to provide an answer to that question.
Though your request is very common, regrettably, SQL does not have a native way to convert a table (the Roles column) to a comma delimited string. This normally isn't a problem because you can simply return the Roles field as
{
UserName = u.UserName,
RolesList = string.Join(", ",
u.UserRoles.Select(ur => ur.Role.RoleName).ToArray())
}
This will work, surprisingly, even though I just mentioned that there is no equivalent function to string.Join in SQL. That's because LINQ is smart enough to just have SQL return the table and to apply the string.Join() at the last minute, in memory.
The problem comes when you then try to sort by the RoleList field as it is created in memory and not in SQL. If you do you'll get the following error message.
NotSupportedException: Method
'System.String Join(System.String,
System.String[])' has no supported
translation to SQL.
This leaves you with two choices:
Write a stored procedure to do this that utilizes a custom function to convert a table to a comma separated list.
OR bring the entire result set back into memory by returning it as .ToList() and then performing the sort ie (/my whole query/).ToList().OrderBy(q => q.Roles);
The second option will be much slower if you have a large dataset. If you user list is never going to grow very large or this query will only get called infrequently when an admin loads the user management screen, then the memory option may not be noticeably slower; however, if this query will be called frequently and/or the user table will get large, then this may not be a viable option.
I would suggest a third option. Reappraise the requirements. In this case, the user may really need a filtering feature where they can look at all users who are in a, b, c roles. If that is the true need, then sorting is not only much harder to implement, but it may also be a worse solution.
Good luck!
Related
I am having difficulty trying to use LINQ to query a sql database in such a way to group all objects (b) in one table associated with an object (a) in another table into an anonymous type with both (a) and a list of (b)s. Essentially, I have a database with a table of offers, and another table with histories of actions taken related to those offers. What I'd like to be able to do is group them in such a way that I have a list of an anonymous type that contains every offer, and a list of every action taken on that offer, so the signature would be:
List<'a>
where 'a is new { Offer offer, List<OfferHistories> offerHistories}
Here is what I tried initially, which obviously will not work
var query = (from offer in context.Offers
join offerHistory in context.OffersHistories on offer.TransactionId equals offerHistory.TransactionId
group offerHistory by offerHistory.TransactionId into offerHistories
select { offer, offerHistories.ToList() }).ToList();
Normally I wouldn't come to SE with this little information but I have tried many different ways and am at a loss for how to proceed.
Please try to avoid .ToList() calls, only do if really necessary. I have an important question: Do you really need all columns of OffersHistories? Because it is very expensive grouping a full object, try only grouping the necessary columns instead. If you really need all offerHistories for one offer then I'm suggesting to write a sub select (this is also cost more performance):
var query = (from offer in context.Offers
select new { offer, offerHistories = (from offerHistory in context.OffersHistories
where offerHistory.TransactionId == offer.TransactionId
select offerHistory) });
P.s.: it's a good idea to create indexes for foreign key columns, columns that are used in where and group by statements, those are going to make the query faster,
Profiling my code because it is taking a long time to execute, it is generating a SELECT instead of a COUNT and as there are 20,000 records it is very very slow.
This is the code:
var catViewModel= new CatViewModel();
var catContext = new CatEntities();
var catAccount = catContext.Account.Single(c => c.AccountId == accountId);
catViewModel.NumberOfCats = catAccount.Cats.Count();
It is straightforward stuff, but the code that the profiler is showing is:
exec sp_executesql N'SELECT
[Extent1].xxxxx AS yyyyy,
[Extent1].xxxxx AS yyyyy,
[Extent1].xxxxx AS yyyyy,
[Extent1].xxxxx AS yyyyy // You get the idea
FROM [dbo].[Cats] AS [Extent1]
WHERE Cats.[AccountId] = #EntityKeyValue1',N'#EntityKeyValue1 int',#EntityKeyValue1=7
I've never seen this behaviour before, any ideas?
Edit: It is fixed if I simply do this instead:
catViewModel.NumberOfRecords = catContext.Cats.Where(c => c.AccountId == accountId).Count();
I'd still like to know why the former didn't work though.
So you have 2 completely separate queries going on here and I think I can explain why you get different results. Let's look at the first one
// pull a single account record
var catAccount = catContext.Account.Single(c => c.AccountId == accountId);
// count all the associated Cat records against said account
catViewModel.NumberOfCats = catAccount.Cats.Count();
Going on the assumption that Cats has a 0..* relationship with Account and assuming you are leveraging the frameworks ability to lazily load foreign tables then your first call to catAccounts.Cats is going to result in a SELECT for all the associated Cat records for that particular account. This results in the table being brought into memory therefore the call to Count() would result in an internal check of the Count property of the in-memory collection (hence no COUNT SQL generated).
The second query
catViewModel.NumberOfRecords =
catContext.Cats.Where(c => c.AccountId == accountId).Count();
Is directly against the Cats table (which would be IQueryable<T>) therefore the only operations performed against the table are Where/Count, and both of these will be evaluated on the DB-side before execution so it's obviously a lot more efficient than the first.
However, if you need both Account and Cats then I would recommend you eager load the data on the fetch, that way you take the hit upfront once
var catAccount = catContext.Account.Include(a => a.Cats).Single(...);
Most times, when somebody accesses a sub-collection of an entity, it is because there are a limited number of records, and it is acceptable to populate the collection. Thus, when you access:
catAccount.Cats
(regardless of what you do next), it is filling that collection. Your .Count() is then operating on the local in-memory collection. The problem is that you don't want that. Now you have two options:
check whether your provider offer some mechanism to make that a query rather than a collection
build the query dynamically
access the core data-model instead
I'm pretty confident that if you did:
catViewModel.NumberOfRecords =
catContext.Cats.Count(c => c.AccountId == accountId);
it will work just fine. Less convenient? Sure. But "works" is better than "convenient".
Please advise,, ALL the resources I've perused , books, videos tutorials none have what I would assume to be a simple, common scenario.
-----What is the Most efficient and standard way of querying through bridge tables with Link To Entities-----
I'm trying to to a query with Linq To Entities.I is my understanding that LinqToSQL is deprecated even if informally..
I have a standard
one to many (bridge table)
and the bridge table many to one for the final table
and lastly a specific key for the original table
If it helps there's a User table atop this mess with a one to many user to roles
Tables:
User, ( Not in the Query I have a specific KeyId for this table for the where clause),
Role,
&
RolePermission, ( Bridge / map whatever you want to call it, it has FK's for Role & Permission )
[unfortunately it has other cols or this wouldn't be so stressful. I.E. its not
abstracted in the entity framework its actually present],
& Permission.
Summary I want every permission for every role for this user, User ID lets say 5
I have a list of roles per user so (to start) I intended on feeding this query the role ID and calling it multiple times ,,, appending its results to a list 4Ea RoleId This is assuredly not ideal
Ideal would be to utilize the UserID & RoleID in a single query...
How do you do Multiple Inner Joins in Linq to Entities
This link above claimed that you can just pull his off by requesting all the tables involved without specifically joining them the PK-FK fields ?!?!?! Say What ??!
This is Just Peuedo code folks I've typed up several dozen attempts at this scenario
parameter p_RoleId --- potential version of this could get RoleId's per passed in UserID ideally
List<Permissions> listToReturn = new List<Permissions>();
var result=(from p in context.Permissions
from rp in m.RolePermissions
where m.roleID = p_RoleId
listToReturn result.ToList();
I really just want to know how to correctly hook these tables together and specify some where clause.
Two table examples are everywhere.. but there or four when you have a bridge table I found nothing
try this
var result = (from p in context.Permissions
join px in m.RolePermissions on p.roleID equals px.roleID
select p);
Well you can definitely do what you are writing in your pseudo code. After all what you do is an implied join. But it might not be very efficient.
You are essentially doing a
select <something> from Permissions , RolePermissions where permissions.id = permissionsRoles.id;
And you do not need a foreign key for that. However in your case you have many to many
//with the assumption that you have a
//navigation property between roles and rolepermissions in your model
var result = connection.Permissions
.Where(i => i.Id == RolePermissionsid)that
.SelectMany(i => i.RolePermissions).ToList();
Is this what you want?
var result=(from p in context.Permissions
join rp in m.RolePermissions on p.RoleId equals rp.RoleId
select p)
I have a simple foreach loop that goes through the productID's I have stored in a user's basket and looks up the product's details from the database.
As you can see from my code, what I have at present will return the very last item on screen - as the variable is overwritten within the loop. I'd like to be able to concat this so that I can display the product details for the items only in the basket.
I know I could do something very easy like store only ProductIDs in the repeater I use and onitemdatabound call the database there but I'd like to make just one database call if possible.
Currently I have the following (removed complex joins from example, but if this matters let me know):
IQueryable productsInBasket = null;
foreach (var thisproduct in store.BasketItems)
{
productsInBasket = (from p in db.Products
where p.Active == true && p.ProductID == thisproduct.ProductID
select new
{
p.ProductID,
p.ProductName,
p.BriefDescription,
p.Details,
p.ProductCode,
p.Barcode,
p.Price
});
}
BasketItems.DataSource = productsInBasket;
BasketItems.DataBind();
Thanks for your help!
It sounds like you really want something like:
var productIds = store.BasketItems.Select(x => x.ProductID).ToList();
var query = from p in db.Products
where p.Active && productIds.Contains(p.ProductID)
select new
{
p.ProductID,
p.ProductName,
p.BriefDescription,
p.Details,
p.ProductCode,
p.Barcode,
p.Price
};
In Jon's answer, which works just fine, the IQueryable will however be converted to an IEnumerable, since you call ToList() on it. This will cause the query to be executed and the answer retrieved. For your situation, this may be OK, since you want to retrieve products for a basket, and where the number of products will probably be considerably small.
I am, however, facing a similar situation, where I want to retrieve friends for a member. Friendship depends on which group two members belongs to - if they share at least one group, they are friends. I thus have to retrieve all membership for all groups for a certain member, then retrieve all members from those groups.
The ToList-approach will not be applicable in my case, since that would execute the query each time I want to handle my friends in various ways, e.g. find stuff that we can share. Retrieving all members from the database, instead of just working on the query and execute it at the last possible time, will kill performance.
Still, my first attempt at this situation was to do just this - retrieve all groups I belonged to (IQueryable), init an List result (IEnumerable), then loop over all groups and append all members to the result if they were not already in the list. Finally, since my interface enforced that an IQueryable was to be returned, I returned the list with AsIQueryable.
This was a nasty piece of code, but at least it worked. It looked something like this:
var result = new List<Member>();
foreach (var group in GetGroupsForMember(member))
result.AddRange(group.GroupMembers.Where(x => x.MemberId != member.Id && !result.Contains(x.Member)).Select(groupMember => groupMember.Member));
return result.AsQueryable();
However, this is BAD, since I add ALL shared members to a list, then convert the list to an IQueryable just to satisfy my post condition. I will retrieve all members that are affected from the database, every time I want to do stuff with them.
Imagine a paginated list - I would then just want to pick out a certain range from this list. If this is done with an IQueryable, the query is just completed with a pagination statement. If this is done with an IEnumerable, the query has already been executed and all operations are applied to the in-memory result.
(As you may also notice, I also navigate down the entity's relations (GroupMember => Member), which increases coupling can cause all kinds of nasty situations further on. I wanted to remove this behavior as well).
So, tonight, I took another round and ended up with a much simpler approach, where I select data like this:
var groups = GetGroupsForMember(member);
var groupMembers = GetGroupMembersForGroups(groups);
var memberIds = groupMembers.Select(x => x.MemberId);
var members = memberService.GetMembers(memberIds);
The two Get methods honor the IQueryable and never convert it to a list or any other IEnumerable. The third line just performs a LINQ query ontop of the IEnumerable. The last line just takes the member IDs and retrieves all members from another service, which also works exclusively with IQueryables.
This is probably still horrible in terms of performance, but I can optimize it further later on, if needed. At least, I avoid loading unnecessary data.
Let me know if I am terribly wrong here.
My database structure is this: an OptiUser belongs to multiple UserGroups through the IdentityMap table, which is a matching table (many to many) with some additional properties attached to it. Each UserGroup has multiple OptiDashboards.
I have a GUID string which identifies a particular user (wlid in this code). I want to get an IEnumerable of all of the OptiDashboards for the user identified by wlid.
Which of these two Linq-to-Entities queries is the most efficient? Do they run the same way on the back-end?
Also, can I shorten option 2's Include statements to just .Include("IdentityMaps.UserGroup.OptiDashboards")?
using (OptiEntities db = new OptiEntities())
{
// option 1
IEnumerable<OptiDashboard> dashboards = db.OptiDashboards
.Where(d => d.UserGroups
.Any(u => u.IdentityMaps
.Any(i => i.OptiUser.WinLiveIDToken == wlid)));
// option 2
OptiUser user = db.OptiUsers
.Include("IdentityMaps")
.Include("IdentityMaps.UserGroup")
.Include("IdentityMaps.UserGroup.OptiDashboards")
.Where(r => r.WinLiveIDToken == wlid).FirstOrDefault();
// then I would get the dashboards through user.IdentityMaps.UserGroup.OptiDashboards
// (through foreach loops...)
}
You may be misunderstanding what the Include function actually does. Option 1 is purely a query syntax which has no effect on what is returned by the entity framework. Option 2, with the Include function instructs the entity framework to Eagerly Fetch the related rows from the database when returns the results of the query.
So option 1 will result in some joins, but the "select" part of the query will be restricted to the OptiDashboards table.
Option 2 will result in joins as well, but in this case it will be returning the results from all the included tables, which obviously is going to introduce more of a performance hit. But at the same time, the results will include all the related entities you need, avoiding the [possible] need for more round-trips to the database.
I think the Include will render as joins an you will the able to access the data from those tables in you user object (Eager Loading the properties).
The Any query will render as exists and not load the user object with info from the other tables.
For best performance if you don't need the additional info use the Any query
As has already been pointed out, the first option would almost certainly perform better, simply because it would be retrieving less information. Besides that, I wanted to point out that you could also write the query this way:
var dashboards =
from u in db.OptiUsers where u.WinLiveIDToken == wlid
from im in u.IdentityMaps
from d in im.UserGroup.OptiDashboards
select d;
I would expect the above to perform similarly to the first option, but you may (or may not) prefer the above form.