Linq to Entities - clause SQL «IN»

230

Dans T-SQL, vous pouvez avoir une requête comme:

SELECT * FROM Users WHERE User_Rights IN ("Admin", "User", "Limited")

Comment répliqueriez-vous cela dans une requête LINQ to Entities? Est-ce même possible?

StevenMcD
la source

Réponses:

349

Vous devez le mettre sur la tête en termes de la façon dont vous y pensez. Au lieu de faire "in" pour trouver les droits d'utilisateur de l'élément actuel dans un ensemble prédéfini de droits d'utilisateur applicables, vous demandez un ensemble prédéfini de droits d'utilisateur s'il contient la valeur applicable de l'élément actuel. C'est exactement de la même manière que vous trouveriez un élément dans une liste régulière dans .NET.

Il existe deux façons de procéder à l'aide de LINQ, l'une utilise la syntaxe de requête et l'autre utilise la syntaxe de méthode. Essentiellement, ils sont identiques et pourraient être utilisés de manière interchangeable selon votre préférence:

Syntaxe de requête:

var selected = from u in users
               where new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights)
               select u

foreach(user u in selected)
{
    //Do your stuff on each selected user;
}

Syntaxe de la méthode:

var selected = users.Where(u => new[] { "Admin", "User", "Limited" }.Contains(u.User_Rights));

foreach(user u in selected)
{
    //Do stuff on each selected user;
}

Ma préférence personnelle dans cette instance pourrait être la syntaxe de la méthode car au lieu d'affecter la variable, je pourrais faire le foreach sur un appel anonyme comme celui-ci:

foreach(User u in users.Where(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}

Syntaxiquement, cela semble plus complexe, et vous devez comprendre le concept d'expressions lambda ou de délégués pour vraiment comprendre ce qui se passe, mais comme vous pouvez le voir, cela condense assez le code.

Tout dépend de votre style de codage et de vos préférences - les trois exemples font la même chose légèrement différemment.

Une autre méthode n'utilise même pas LINQ, vous pouvez utiliser la même syntaxe de méthode en remplaçant «où» par «FindAll» et obtenir le même résultat, qui fonctionnera également dans .NET 2.0:

foreach(User u in users.FindAll(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}
BenAlabaster
la source
j'ai peut-être été trop rapide pour marquer comme réponse, mais je n'ai pas de .Contains après que le {"Admin", "User", "Limited"} VS2008 n'aime pas du tout ce code.
StevenMcD
1
fidèle à mon nom "FailBoy", je l'ai compris: PI mis dans une chaîne [] puis utilisé et cela a fonctionné. Merci!
StevenMcD
désolé, j'ai oublié de refaire le tableau anonyme;) J'ai corrigé mon exemple de code. Heureux que vous l'ayez compris par vous-même.
BenAlabaster
28
Cette réponse aurait été correcte si la question avait porté sur Linq-to-SQL ou Linq en général. Cependant, comme il dit spécifiquement "Linq-to-Entities", cette réponse est incorrecte. array.Contains n'est pas (encore) pris en charge par Linq-to-Entities.
KristoferA
6
@KristoferA - cela peut être vrai pour les versions antérieures d'EF, mais cela me semble correct avec EF4.
Drew Noakes
21

Cela devrait suffire à votre objectif. Il compare deux collections et vérifie si une collection a les valeurs correspondant à celles de l'autre collection

fea_Features.Where(s => selectedFeatures.Contains(s.feaId))
Balaji Birajdar
la source
9

Si vous utilisez VS2008 / .net 3.5, consultez l'astuce # 8 d'Alex James: http://blogs.msdn.com/alexj/archive/2009/03/26/tip-8-writing-where-in-style -queries-using-linq-to-entity.aspx

Sinon, utilisez simplement la méthode array.Contains (someEntity.Member).

KristoferA
la source
Veuillez éviter d'utiliser des réponses de lien uniquement, vous devez résumer le contenu du lien dans votre réponse, au cas où le lien se briserait à l'avenir.
8

Je vais opter pour Inner Join dans ce contexte. Si j'aurais utilisé Contient, il répéterait 6 fois malgré le fait qu'il n'y ait qu'une seule correspondance.

var desiredNames = new[] { "Pankaj", "Garg" }; 

var people = new[]  
{  
    new { FirstName="Pankaj", Surname="Garg" },  
    new { FirstName="Marc", Surname="Gravell" },  
    new { FirstName="Jeff", Surname="Atwood" }  
}; 

var records = (from p in people join filtered in desiredNames on p.FirstName equals filtered  select p.FirstName).ToList(); 

Inconvénients de Contient

Supposons que j'ai deux objets de liste.

List 1      List 2
  1           12
  2            7
  3            8
  4           98
  5            9
  6           10
  7            6

En utilisant Contains, il recherchera chaque élément de la liste 1 dans la liste 2, ce qui signifie que l'itération se produira 49 fois !!!

Pankaj
la source
5
Cela ignore complètement le fait que l'instruction est traduite en SQL. Voyez ici .
Gert Arnold
5

Cela pourrait être la manière possible d'utiliser directement les méthodes d'extension LINQ pour vérifier la clause in

var result = _db.Companies.Where(c => _db.CurrentSessionVariableDetails.Select(s => s.CompanyId).Contains(c.Id)).ToList();
Torakami
la source
2

J'ai également essayé de travailler avec une chose semblable à SQL-IN - interroger un modèle de données d'entité . Mon approche est un constructeur de chaînes pour composer une grosse expression OR. C'est terriblement moche, mais je crains que ce soit la seule façon de procéder en ce moment.

Eh bien, cela ressemble à ceci:

Queue<Guid> productIds = new Queue<Guid>(Products.Select(p => p.Key));
if(productIds.Count > 0)
{
    StringBuilder sb = new StringBuilder();
    sb.AppendFormat("{0}.ProductId = Guid\'{1}\'", entities.Products.Name, productIds.Dequeue());
    while(productIds.Count > 0)
    {
        sb.AppendFormat(" OR {0}.ProductId = Guid\'{1}\'",
          entities.Products.Name, productIds.Dequeue());
    }
}

Utilisation des GUID dans ce contexte : Comme vous pouvez le voir ci-dessus, il y a toujours le mot "GUID" avant le GUID ifself dans les fragments de chaîne de requête. Si vous n'ajoutez pas cela, ObjectQuery<T>.Wherelève l'exception suivante:

Les types d'argument 'Edm.Guid' et 'Edm.String' sont incompatibles pour cette opération., Expression presque égale, ligne 6, colonne 14.

J'ai trouvé cela dans les forums MSDN.

Matthias

... dans l'attente de la prochaine version de .NET et Entity Framework, quand tout ira mieux. :)

Matthias Meid
la source
2

Une méthode alternative à la réponse BenAlabaster

Tout d'abord, vous pouvez réécrire la requête comme ceci:

var matches = from Users in people
        where Users.User_Rights == "Admin" ||
              Users.User_Rights == "Users" || 
              Users.User_Rights == "Limited"
        select Users;

Certes, c'est plus «verbeux» et pénible à écrire mais ça marche tout de même.

Donc, si nous avions une méthode utilitaire qui facilitait la création de ce type d'expressions LINQ, nous serions en affaires.

avec une méthode utilitaire en place, vous pouvez écrire quelque chose comme ceci:

var matches = ctx.People.Where(
        BuildOrExpression<People, string>(
           p => p.User_Rights, names
        )
);

Cela crée une expression qui a le même effet que:

var matches = from p in ctx.People
        where names.Contains(p.User_Rights)
        select p;

Mais ce qui, plus important, fonctionne réellement contre .NET 3.5 SP1.

Voici la fonction de plomberie qui rend cela possible:

public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(
        Expression<Func<TElement, TValue>> valueSelector, 
        IEnumerable<TValue> values
    )
{     
    if (null == valueSelector) 
        throw new ArgumentNullException("valueSelector");

    if (null == values)
        throw new ArgumentNullException("values");  

    ParameterExpression p = valueSelector.Parameters.Single();

    if (!values.Any())   
        return e => false;

    var equals = values.Select(value =>
        (Expression)Expression.Equal(
             valueSelector.Body,
             Expression.Constant(
                 value,
                 typeof(TValue)
             )
        )
    );
   var body = equals.Aggregate<Expression>(
            (accumulate, equal) => Expression.Or(accumulate, equal)
    ); 

   return Expression.Lambda<Func<TElement, bool>>(body, p);
}

Je ne vais pas essayer d'expliquer cette méthode, sinon de dire qu'elle construit essentiellement une expression de prédicat pour toutes les valeurs en utilisant valueSelector (c'est-à-dire p => p.User_Rights) et OU ces prédicats ensemble pour créer une expression pour l'ensemble prédicat

Source: http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx

feu dans le trou
la source
0

Exemple réel:

var trackList = Model.TrackingHistory.GroupBy(x => x.ShipmentStatusId).Select(x => x.Last()).Reverse();
List<int> done_step1 = new List<int>() {2,3,4,5,6,7,8,9,10,11,14,18,21,22,23,24,25,26 };
bool isExists = trackList.Where(x => done_step1.Contains(x.ShipmentStatusId.Value)).FirstOrDefault() != null;
Adel Mourad
la source
-14

Sérieusement? Vous n'avez jamais utilisé

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)
cjm30305
la source
9
-1 Essayez ceci avec 20 valeurs ou plus dans un tableau de plus de 1000 lignes et vous verrez rapidement l'avantage de la solution acceptée. De plus, il n'est pas facile d'ajouter un nombre arbitraire de conditions à l'instruction where (comme si l'utilisateur choisit d'inclure les options 1 et 2, mais pas 3).
Trisped
Eh bien, je n'avais besoin d'aucun des trucs de savant fou et cette réponse a été votée parce que j'avais besoin d'un ET et de 2 ORS var SamplePoints = (de c dans _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x => x.WSFStateCode) où c. PWS == id && (((c.WSFStateCode.Substring (0, 2) == "SR") || (c.WSFStateCode.Substring (0, 2) == "CH")) sélectionnez c) .ToList () ;
JustJohn
@ Trisped - le nombre de lignes (1000) ne change rien - ou est-ce que je manque quelque chose?
tymtam
@Tymski Oui, le nombre de lignes compte. Plus il y a de lignes, plus il y a de calculs. Même avec le nombre de valeurs possibles: Checks = NumValues * NumRows. Comme il s'agit d'un calcul de type M * N, si l'un ou l'autre est petit, le temps pour effectuer chaque vérification requise sera également petit. J'ai ajouté la contrainte pour que cjm30305 sache comment configurer un environnement de test où montrer pourquoi sa solution est mauvaise.
Trisped
@Trisped Dites-vous que cela where new[] { 1, 2, 3 }.Contains(x)fait moins de comparaisons alors where (x == 1 || x == 2 || x == 3)?
tymtam