Linq to SQL comment faire "où [colonne] dans (liste de valeurs)"

101

J'ai une fonction où j'obtiens une liste d'identifiants et je dois renvoyer une liste correspondant à une description associée à l'identifiant. Par exemple:

public class CodeData
{
    string CodeId {get; set;}
    string Description {get; set;}
}

public List<CodeData> GetCodeDescriptionList(List<string> codeIDs)
    //Given the list of institution codes, return a list of CodeData
    //having the given CodeIds
}

Donc, si je créais moi-même le sql pour cela, je ferais simplement quelque chose comme ce qui suit (où la clause in contient toutes les valeurs de l'argument codeIds):

Select CodeId, Description FROM CodeTable WHERE CodeId IN ('1a','2b','3')

Dans Linq to Sql, je n'arrive pas à trouver l'équivalent de la clause "IN". Le meilleur que j'ai trouvé jusqu'à présent (qui ne fonctionne pas) est:

 var foo = from codeData in channel.AsQueryable<CodeData>()
           where codeData.CodeId == "1" || codeData.CodeId == "2"
           select codeData;

Le problème étant que je ne peux pas générer dynamiquement une liste de clauses "OR" pour linq à sql, car elles sont définies au moment de la compilation.

Comment accomplir une clause where qui vérifie qu'une colonne est dans une liste dynamique de valeurs en utilisant Linq to Sql?

Nathan
la source

Réponses:

159

Utilisation

where list.Contains(item.Property)

Ou dans votre cas:

var foo = from codeData in channel.AsQueryable<CodeData>()
          where codeIDs.Contains(codeData.CodeId)
          select codeData;

Mais vous pourriez aussi bien le faire en notation par points:

var foo = channel.AsQueryable<CodeData>()
                 .Where(codeData => codeIDs.Contains(codeData.CodeId));
Jon Skeet
la source
comment utiliser en cas de CodeId est Integer ??
Kiran Solkar
2
@KiranSolkar: Alors ce codeIDsserait probablement un List<int>, et tout irait bien.
Jon Skeet
@JonSkeet N'est-ce pas sensible à la casse? Si codeIDs est une liste de chaînes majuscules et codeData.codeId est une chaîne minuscule, cela échouera.
PersyJack
@PersyJack: Il n'y avait rien dans la question sur le fait qu'il doive être insensible à la casse. Quant à savoir si ce serait le cas ou non, je ne me souviens pas si LINQ to SQL applique le respect de la casse par défaut ou laisse les paramètres de base de données le gouverner.
Jon Skeet
1
@PersyJack LINQ to SQL génère la requête T-SQL, qui s'exécute ensuite sur SQL Server en utilisant les paramètres de base de données pour respecter la casse. Bien que, si l'on ne fait pas attention et que l'on matérialise les résultats de la requête, avant d'appliquer LINQ aux objets en mémoire, ils peuvent subir les conséquences d'une non-concordance de la casse.
Zarepheth le
26

Vous pouvez également utiliser:

List<int> codes = new List<int>();

codes.add(1);
codes.add(2);

var foo = from codeData in channel.AsQueryable<CodeData>()
          where codes.Any(code => codeData.CodeID.Equals(code))
          select codeData;
Nick DeMayo
la source
1
J'ai dû l'utiliser car notre implémentation d'IQToolkit ne prend pas en charge .Contains ()
DJ van Wyk
1

J'avais utilisé la méthode dans la réponse de Jon Skeet, mais une autre m'est venue à l'esprit Concat. La Concatméthode a légèrement mieux fonctionné dans un test limité, mais c'est un problème et je vais probablement m'en tenir à Contains, ou peut-être que j'écrirai une méthode d'aide pour le faire pour moi. Quoi qu'il en soit, voici une autre option si quelqu'un est intéressé:

La méthode

// Given an array of id's
var ids = new Guid[] { ... };

// and a DataContext
var dc = new MyDataContext();

// start the queryable
var query = (
    from thing in dc.Things
    where thing.Id == ids[ 0 ]
    select thing 
);

// then, for each other id
for( var i = 1; i < ids.Count(); i++ ) {
    // select that thing and concat to queryable
    query.Concat(
        from thing in dc.Things
        where thing.Id == ids[ i ]
        select thing
    );
}

Test de performance

Ce n'était pas à distance scientifique. J'imagine que la structure de votre base de données et le nombre d'identifiants impliqués dans la liste auraient un impact significatif.

J'ai mis en place un test où j'ai fait 100 essais chacun Concatet Containsoù chaque essai impliquait la sélection de 25 lignes spécifiées par une liste randomisée de clés primaires. Je l'ai exécuté environ une douzaine de fois, et la plupart du temps, la Concatméthode est 5 à 10% plus rapide, bien qu'une fois la Containsméthode ait gagné par juste un brin.

DCShannon
la source
0
 var filterTransNos = (from so in db.SalesOrderDetails
                    where  ItemDescription.Contains(ItemDescription)
                            select new { so.TransNo }).AsEnumerable();    


listreceipt = listreceipt.Where(p => filterTransNos.Any(p2 => p2.TransNo == p.TransNo)).ToList();
Deepan Raj
la source
-1

Voici comment je le fais en utilisant HashSet

        HashSet<String> hs = new HashSet<string>(new String[] { "Pluto", "Earth", "Neptune" });
        String[] arr =
        {
            "Pluto",
            "Earth",
            "Neptune",
            "Jupiter",
            "Saturn",
            "Mercury",
            "Pluto",
            "Earth",
            "Neptune",
            "Jupiter",
            "Saturn",
            "Mercury",
            // etc.
        };
        ICollection<String> coll = arr;

        String[] arrStrFiltered = coll.Where(str => hs.Contains(str)).ToArray();

HashSet est essentiellement presque à O (1) donc votre complexité reste O (n).

MG
la source
Il s'agit de LINQ-to-SQL. De telles considérations LINQ-to-objects ne s'appliquent pas.
Gert Arnold
ICollection peut également provenir d'un LINQ-SQL, c'est un moyen générique
MG
La question est de savoir comment créer une expression qui se traduit par un SQL correct. Cela n'a rien à voir avec la façon de rechercher une collection locale. Votre réponse ne fera que tromper les futurs lecteurs qui ne sont pas conscients de cette distinction.
Gert Arnold