web-dev-qa-db-fra.com

Comment faire une sous-requête dans LINQ?

Voici un exemple de requête que je tente de convertir en LINQ:

SELECT *
FROM Users
WHERE Users.lastname LIKE '%fra%'
    AND Users.Id IN (
         SELECT UserId 
         FROM CompanyRolesToUsers 
         WHERE CompanyRoleId in (2,3,4) )

Il existe une relation FK entre CompanyRolesToUsers et Users, mais la relation est multiple à multiple et CompanyRolesToUsers correspond à la table de jonction.

La plupart de nos sites sont déjà construits et la plupart des filtres fonctionnent déjà en construisant des expressions à l'aide d'une classe PredicateExtensions.

Le code pour les filtres simples ressemble à ceci:

 if (!string.IsNullOrEmpty(TextBoxLastName.Text))
 {
     predicateAnd = predicateAnd.And(c => c.LastName.Contains(
                                     TextBoxLastName.Text.Trim()));
 }

e.Result = context.Users.Where(predicateAnd);

J'essaie d'ajouter un prédicat pour une sous-sélection dans une autre table. (CompanyRolesToUsers)

Ce que j'aimerais pouvoir ajouter est quelque chose qui fait ceci:

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);
}

Est-ce qu'il y a un moyen de faire ça? C'est frustrant parce que je peux écrire facilement la procédure stockée, mais je suis novice en ce qui concerne LINQ et j'ai une date limite. Je n'ai pas été capable de trouver un exemple qui corresponde, mais je suis sûr qu'il est là quelque part.

65
marcel_g

Voici une sous-requête pour vous!

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

En ce qui concerne cette partie de la question:

predicateAnd = predicateAnd.And(c => c.LastName.Contains(
                                TextBoxLastName.Text.Trim()));

Je recommande fortement d'extraire la chaîne de la zone de texte avant de créer la requête.

string searchString = TextBoxLastName.Text.Trim();
predicateAnd = predicateAnd.And(c => c.LastName.Contains( searchString));

Vous voulez garder un bon contrôle sur ce qui est envoyé à la base de données. Dans le code d'origine, une lecture possible est qu'une chaîne non limitée est envoyée à la base de données pour être ajustée - ce qui n'est pas un travail satisfaisant pour la base de données.

77
Amy B

Il n’ya pas de sous-requête nécessaire avec cette déclaration, qui est mieux écrite comme suit:

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)

ou

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)

Cela étant dit, dans LINQ ce serait

from u in Users
from c in CompanyRolesToUsers 
where u.Id == c.UserId &&
      u.LastName.Contains("fra") &&
      selectedRoles.Contains(c.CompanyRoleId)
select u

ou

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

Encore une fois, ce sont deux manières respectables de représenter cela. Je préfère la syntaxe explicite "join" dans les deux cas moi-même, mais la voici ...

22
TheSoftwareJedi

C’est comme ça que j’ai fait les sous-requêtes dans LINQ, je pense que cela devrait donner ce que vous voulez. Vous pouvez remplacer l'explicite CompanyRoleId == 2 ... par une autre sous-requête pour les différents rôles souhaités ou bien la rejoindre également.

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;
5
Noah

Ok, voici une requête de jointure de base qui obtient les enregistrements corrects:

   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();
}

Mais voici le code qui s'intègre le plus facilement à l'algorithme que nous avions déjà en 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) ); 
        } 

qui est grâce à une affiche à la forum LINQtoSQL

2
marcel_g

Vous pouvez faire quelque chose comme ceci pour votre cas la syntaxe peut être un peu fausse). Regardez aussi ceci --- (lien

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;
2
Perpetualcoder

Voici une version du code SQL qui renvoie les enregistrements corrects:

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)

Notez également que (2, 3, 4) est une liste sélectionnée par l’utilisateur de l’application Web dans une liste de cases à cocher, et j’ai oublié de mentionner que j’ai simplement codé cela dans un souci de simplicité. En réalité, il s’agit d’un tableau de valeurs CompanyRoleId, qui pourrait donc être (1) ou (2,5) ou (1,2,3,4,6,7,99).

L'autre chose que je devrais préciser plus clairement, c'est que les PredicateExtensions sont utilisées pour ajouter de manière dynamique des clauses de prédicat au Where de la requête, en fonction des champs de formulaire remplis par l'utilisateur de l'application Web. transformer la requête de travail en une expression LINQ que je peux attacher à la liste dynamique d’expressions.

Je vais donner quelques exemples de requêtes LINQ et voir si je peux les intégrer à notre code, puis je posterai mes résultats. Merci!

marcel

1
marcel_g