How to do a subquery in LINQ? - c#

Here's an example of the query I'm trying to convert to LINQ:
SELECT *
FROM Users
WHERE Users.lastname LIKE '%fra%'
AND Users.Id IN (
SELECT UserId
FROM CompanyRolesToUsers
WHERE CompanyRoleId in (2,3,4) )
There is a FK relationship between CompanyRolesToUsers and Users, but it's a many to many relationship and CompanyRolesToUsers is the junction table.
We already have most of our site built, and we already have most of the filtering working by building Expressions using a PredicateExtensions class.
The code for the straightforward filters looks something like this:
if (!string.IsNullOrEmpty(TextBoxLastName.Text))
{
predicateAnd = predicateAnd.And(c => c.LastName.Contains(
TextBoxLastName.Text.Trim()));
}
e.Result = context.Users.Where(predicateAnd);
I'm trying to add a predicate for a subselect in another table. (CompanyRolesToUsers)
What I'd like to be able to add is something that does this:
int[] selectedRoles = GetSelectedRoles();
if( selectedRoles.Length > 0 )
{
//somehow only select the userid from here ???:
var subquery = from u in CompanyRolesToUsers
where u.RoleID in selectedRoles
select u.UserId;
//somehow transform this into an Expression ???:
var subExpression = Expression.Invoke(subquery);
//and add it on to the existing expressions ???:
predicateAnd = predicateAnd.And(subExpression);
}
Is there any way to do this? It's frustrating because I can write the stored procedure easily, but I'm new to this LINQ thing and I have a deadline. I haven't been able to find an example that matches up, but I'm sure it's there somewhere.

Here's a subquery for you!
List<int> IdsToFind = new List<int>() {2, 3, 4};
db.Users
.Where(u => SqlMethods.Like(u.LastName, "%fra%"))
.Where(u =>
db.CompanyRolesToUsers
.Where(crtu => IdsToFind.Contains(crtu.CompanyRoleId))
.Select(crtu => crtu.UserId)
.Contains(u.Id)
)
Regarding this portion of the question:
predicateAnd = predicateAnd.And(c => c.LastName.Contains(
TextBoxLastName.Text.Trim()));
I strongly recommend extracting the string from the textbox before authoring the query.
string searchString = TextBoxLastName.Text.Trim();
predicateAnd = predicateAnd.And(c => c.LastName.Contains( searchString));
You want to maintain good control over what gets sent to the database. In the original code, one possible reading is that an untrimmed string gets sent into the database for trimming - which is not good work for the database to be doing.

There is no subquery needed with this statement, which is better written as
select u.*
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId --join just specified here, perfectly fine
and u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)
or
select u.*
from Users u inner join CompanyRolesToUsers c
on u.Id = c.UserId --explicit "join" statement, no diff from above, just preference
where u.lastname like '%fra%'
and c.CompanyRoleId in (2,3,4)
That being said, in LINQ it would be
from u in Users
from c in CompanyRolesToUsers
where u.Id == c.UserId &&
u.LastName.Contains("fra") &&
selectedRoles.Contains(c.CompanyRoleId)
select u
or
from u in Users
join c in CompanyRolesToUsers
on u.Id equals c.UserId
where u.LastName.Contains("fra") &&
selectedRoles.Contains(c.CompanyRoleId)
select u
Which again, are both respectable ways to represent this. I prefer the explicit "join" syntax in both cases myself, but there it is...

This is how I've been doing subqueries in LINQ, I think this should get what you want. You can replace the explicit CompanyRoleId == 2... with another subquery for the different roles you want or join it as well.
from u in Users
join c in (
from crt in CompanyRolesToUsers
where CompanyRoleId == 2
|| CompanyRoleId == 3
|| CompanyRoleId == 4) on u.UserId equals c.UserId
where u.lastname.Contains("fra")
select u;

You could do something like this for your case - (syntax may be a bit off). Also look at this link
subQuery = (from crtu in CompanyRolesToUsers where crtu.RoleId==2 || crtu.RoleId==3 select crtu.UserId).ToArrayList();
finalQuery = from u in Users where u.LastName.Contains('fra') && subQuery.Contains(u.Id) select u;

Ok, here's a basic join query that gets the correct records:
int[] selectedRolesArr = GetSelectedRoles();
if( selectedRolesArr != null && selectedRolesArr.Length > 0 )
{
//this join version requires the use of distinct to prevent muliple records
//being returned for users with more than one company role.
IQueryable retVal = (from u in context.Users
join c in context.CompanyRolesToUsers
on u.Id equals c.UserId
where u.LastName.Contains( "fra" ) &&
selectedRolesArr.Contains( c.CompanyRoleId )
select u).Distinct();
}
But here's the code that most easily integrates with the algorithm that we already had in place:
int[] selectedRolesArr = GetSelectedRoles();
if ( useAnd )
{
predicateAnd = predicateAnd.And( u => (from c in context.CompanyRolesToUsers
where selectedRolesArr.Contains(c.CompanyRoleId)
select c.UserId).Contains(u.Id));
}
else
{
predicateOr = predicateOr.Or( u => (from c in context.CompanyRolesToUsers
where selectedRolesArr.Contains(c.CompanyRoleId)
select c.UserId).Contains(u.Id) );
}
which is thanks to a poster at the LINQtoSQL forum

Here's a version of the SQL that returns the correct records:
select distinct u.*
from Users u, CompanyRolesToUsers c
where u.Id = c.UserId --join just specified here, perfectly fine
and u.firstname like '%amy%'
and c.CompanyRoleId in (2,3,4)
Also, note that (2,3,4) is a list selected from a checkbox list by the web app user, and I forgot to mention that I just hardcoded that for simplicity. Really it's an array of CompanyRoleId values, so it could be (1) or (2,5) or (1,2,3,4,6,7,99).
Also the other thing that I should specify more clearly, is that the PredicateExtensions are used to dynamically add predicate clauses to the Where for the query, depending on which form fields the web app user has filled in. So the tricky part for me is how to transform the working query into a LINQ Expression that I can attach to the dynamic list of expressions.
I'll give some of the sample LINQ queries a shot and see if I can integrate them with our code, and then get post my results. Thanks!
marcel

Related

Multiple table linq query?

Could you help me translate this SQL query in Linq and Lambda expression.
SELECT * FROM User
JOIN UserIdentifier ON User.id = UserIdentifier.user_fk
JOIN UserPassword ON User.id = UserPassword.user_fk
WHERE UserIdentifier.identifier_value = "key" AND UserPassword.password = "1234"
I already wrote this
var query = from u in context.Users
join ui in context.UserIdentifiers on u.id equals ui.user_fk into Joined
from j in Joined.DefaultIfEmpty()
join up in context.UserPasswords on u.id equals up.user_fk into Joined2
from ???
select new { id = u.id, identifier = j.identifier_value, password = Joined2.???}
with help of http://paragy.wordpress.com/2010/11/18/multiple-joins-with-linq-and-lambda/
I'm not a happy user of linq. Actually I don't like linq because of this kind of request. This one is simple but when you try complex request linq is a nightmare. It's even worst with dynamic request. I always have problem with linq syntax and the web doesn't really help. I don't find a correct documentation to write queries.
I guess I'm not the first one to ask this question but all the documentation I found seems wrong or doesn't help me. This is a simple query and I don't find correct help. I'm still waiting someone prove me that link is not just another POC.
You're missing a where clause.
And your SQL joins are regular INNER JOINs, so you don't need these join ... into g from x in g.DefaultIfEmpty() -> that's how you do an equivalent of LEFT OUTER JOIN.
var query = from u in context.Users
join ui in context.UserIdentifiers on u.id equals ui.user_fk
join up in context.UserPasswords on u.id equals up.user_fk
where ui.identifier_value == "key" && up.password == "1234"
select new
{
id = u.id,
identifier = ui.identifier_value,
password = up.password
};

Get a list result from another list and a property

I have the following 3 (simplified) model classes, each of which contains a collection of the other:
Group.CollectionOfPermissions
Group.CollectionOfUsers
User.CollectionOfGroups
User.CollectionOfPermissions
Permission.CollectionOfGroups
Permission.CollectionOfUsers
I have a View that is based off a single User.ID, and I want to be able to return the effective permissions for said user.
The effective permissions are based off:
The individual users' permissions, which is simply the User.CollectionOfPermissions property.
The derived permissions of the groups that the user is a part of. That is, for every Group to which the User belongs to, I need to grab those Permissions as well.
Number 1 is obviously as simple as referencing the collection property.
Number 2 is where I'm having a bit more trouble with a LINQ selection.
I could write a stored proc along the lines of:
SELECT * FROM PERMISSIONS P WHERE P.ID IN
(SELECT PERMISSION_ID FROM PERMISSION_GROUP_REF PGR WHERE PGR.GROUP_ID IN
(SELECT ID FROM GROUPS G WHERE G.ID IN
(SELECT GROUP_ID FROM GROUP_USER_REF GUR WHERE GUR.USER_ID IN
(SELECT ID FROM USERS U WHERE U.ID = #USERID))))
But I'd much rather keep this in line with the rest of the project and continue to use LINQ, especially since I want to avoid directly querying the reference tables in code (given that the collections already exist as class properties). How would I approach this kind of LINQ query?
Edit: This is using Entity Framework 6 with Razor 3
Users.Where(u => u.UserId == userId)
.SelectMany(u => u.CollectionOfPermissions)
.Select (cp=>cp.Permission) // you might need to do this too
.Union(Users.Where(u => u.UserId == userId)
.SelectMany(u => u.CollectionOfGroups)
.SelectMany(cg => cg.Permission))
May be something like this.
EDIT: For reference, this produces the following SQL (slightly different column names in my test rig):-
SELECT
[Distinct1].[C1] AS [C1]
FROM ( SELECT DISTINCT
[UnionAll1].[Permission_Id] AS [C1]
FROM (SELECT
[Extent1].[Permission_Id] AS [Permission_Id]
FROM [dbo].[PermissionPersons] AS [Extent1]
WHERE 1 = [Extent1].[Person_Id]
UNION ALL
SELECT
[Extent3].[Permission_Id] AS [Permission_Id]
FROM [dbo].[PersonGroups] AS [Extent2]
INNER JOIN [dbo].[PermissionGroups] AS [Extent3] ON [Extent2].[Group_Id] = [Extent3].[Group_Id]
WHERE 1 = [Extent2].[Person_Id]) AS [UnionAll1]
) AS [Distinct1]
On another thought, why not query through Permission entity all together?
context.Permissions.Where(p=>
p.Groups.Any(gr=>gr.Users.Any(u=>u.UserId == userId))
|| p.Users.Any(u=>u.UserId == userId))
.Distinct()
The SQL you posted translates to this:
PERMISSIONS.Where(p =>
PERMISSION_GROUP_REF.Where(pg =>
GROUPS.Where(g =>
GROUP_USER_REF.Where(gu => gu.USER_ID == USERID)
.Any(gu => gu.GROUP_ID == g.ID))
.Any(g => g.ID == pg.GROUP_ID))
.Any(pg => pg.PERMISSION_ID == p.ID))
Maybe you can simplify it a bit, but this should work.

using "greater than or equal" operator in linq join operation [duplicate]

I had tried to join two table conditionally but it is giving me syntax error. I tried to find solution in the net but i cannot find how to do conditional join with condition. The only other alternative is to get the value first from one table and make a query again.
I just want to confirm if there is any other way to do conditional join with linq.
Here is my code, I am trying to find all position that is equal or lower than me. Basically I want to get my peers and subordinates.
from e in entity.M_Employee
join p in entity.M_Position on e.PostionId >= p.PositionId
select p;
You can't do that with a LINQ joins - LINQ only supports equijoins. However, you can do this:
var query = from e in entity.M_Employee
from p in entity.M_Position
where e.PostionId >= p.PositionId
select p;
Or a slightly alternative but equivalent approach:
var query = entity.M_Employee
.SelectMany(e => entity.M_Position
.Where(p => e.PostionId >= p.PositionId));
Following:
from e in entity.M_Employee
from p in entity.M_Position.Where(p => e.PostionId >= p.PositionId)
select p;
will produce exactly the same SQL you are after (INNER JOIN Position P ON E..PostionId >= P.PositionId).
var currentDetails = from c in customers
group c by new { c.Name, c.Authed } into g
where g.Key.Authed == "True"
select g.OrderByDescending(t => t.EffectiveDate).First();
var currentAndUnauthorised = (from c in customers
join cd in currentDetails
on c.Name equals cd.Name
where c.EffectiveDate >= cd.EffectiveDate
select c).OrderBy(o => o.CoverId).ThenBy(o => o.EffectiveDate);
If you have a table of historic detail changes including authorisation status and effective date. The first query finds each customers current details and the second query adds all subsequent unauthorised detail changes in the table.
Hope this is helpful as it took me some time and help to get too.

How do I create a linq query that gets everything but a specific value

Say I have a linq query select r in db.Roles where r.RoleName DOES NOT contain ("Administrator") select r;
It is the does not contain part that has me confused. I realize I can do that .Contains call but how do you do the opposite?
Thanks!
Update:
I found the Exclude method and here is how I used it:
var role = (from r in db.Roles
orderby r.RoleName
select r)
.Except(from r in db.Roles
where r.RoleName == "Administrator" & r.RoleName == "DataEntry"
select r
);
Try the following
var query = db.Roles.Where(x => !x.RoleName.Contains("Administrator"));
You can just use the C# not operator ! (exclamation point). Expanded LINQ syntax version
var query =
from it in db.Roles
where !it.RoleName.Contains("Administrator")
select it;
If you had multiple roles to exclude, you might take a look at the Except function.

How do I most elegantly express left join with aggregate SQL as LINQ query

SQL:
SELECT
u.id,
u.name,
isnull(MAX(h.dateCol), '1900-01-01') dateColWithDefault
FROM universe u
LEFT JOIN history h
ON u.id=h.id
AND h.dateCol<GETDATE()-1
GROUP BY u.Id, u.name
A solution, albeit one that defers handling of the null value to the code, could be:
DateTime yesterday = DateTime.Now.Date.AddDays(-1);
var collection=
from u in db.Universe
select new
{
u.id,
u.name,
MaxDate =(DateTime?)
(
from h in db.History
where u.Id == h.Id
&& h.dateCol < yesterday
select h.dateCol
).Max()
};
This does not produce exactly the same SQL, but does provide the same logical result. Translating "complex" SQL queries to LINQ is not always straightforward.
var collection=
from u in db.Universe
select new
{
u.id,
u.name,
MaxDate =(DateTime?)
(
from h in db.History
where u.Id == h.Id
&& h.dateCol < yesterday
select h.dateCol
).Max()
};
Just youse the above code and this should work fine!
You're going to want to use the join into construct to create a group query.
TestContext db = new TestContext(CreateSparqlTripleStore());
var q = from a in db.Album
join t in db.Track on a.Name equals t.AlbumName into tracks
select new Album{Name = a.Name, Tracks = tracks};
foreach(var album in q){
Console.WriteLine(album.Name);
foreach (Track track in album.Tracks)
{
Console.WriteLine(track.Title);
}
}
This isn't a full answer for you, but on the left join piece you can use the DefaultIfEmpty operator like so:
var collection =
from u in db.Universe
join history in db.History on u.id = history.id into temp
from h in temp.DefaultIfEmpty()
where h.dateCol < DateTime.Now.Date.AddDays(-1)
select u.id, u.name, h.dateCol ?? '1900-01-01'
I haven't had the need to do any groupby commands yet, so I left that out as to not send you down the wrong path. Two other quick things to note. I have been unable to actually join on two parameters although as above there are ways to get around it. Also, the ?? operator works really well in place of the isnull in SQL.

Categories

Resources