views:

8115

answers:

6

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.

TIA,
Marcel

A: 

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;
Noah
No subquery needed here! Teach the man some sql! :)
TheSoftwareJedi
He asked for a subquery!But I'm sure LINQ changes it all to the same SQL anyway...
Noah
+6  A: 

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...

TheSoftwareJedi
A very nice answer, but all these methods behave differently and usually produce very different sql, sql that might perform good or bad, usually using a straight join produces the best sql, but not always ...
Pop Catalin
not "very different sql", just "different sql". I won't argue the differences, but in my experience, there is none as it pertains to performance. Any optimizer can figure out the joins from the where clause.
TheSoftwareJedi
In my experience for reasonably complex queries, the way you write linq queries makes a difference sql server 2005 sometimes doesn't produce an optimal query plan for queries with deep levels of subquery nesting ... I had to optimize some complex linq queries more than a few times ...
Pop Catalin
Thanks for the help. While this does help me understand how to translate SQL into LINQ queries, your starting queries return different results -> if a User has multiple roles, then their record will be returned multiple times, which is why I avoided the join in the first place.
marcel_g
Actually, if I add a 'distinct' to the SQL, then I get the correct records. I'll add another post with the working join.
marcel_g
Hmm, why "filter" when you can "join and distinct". Is it because "join and distinct" are natural and exciting sql while "filter" just isn't exciting enough?
David B
add a distinct... it's quite easy... don't be a whiner. Run the plans, give me facts, then come back and bitch.
TheSoftwareJedi
+1  A: 

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;
Perpetualcoder
No subquery needed here! Teach the man some sql!
TheSoftwareJedi
I just meant to show he wanted to do it..not enforce my own opinion
Perpetualcoder
A: 

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

marcel_g
+2  A: 

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.

David B
Thanks for the helpful code. That certainly shows how things can be chained together. You are also probably right about performing the string operation before handing it to Linq, it certainly wouldn't hurt to keep things separated.
marcel_g
+2  A: 

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

marcel_g