Sélectionnez plusieurs enregistrements en fonction de la liste des identifiants avec linq

122

J'ai une liste contenant les identifiants de ma UserProfiletable. Comment puis-je tout sélectionner en UserProfilesfonction de la liste des identifiants que j'ai obtenus lors d'une varutilisation LINQ?

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(......);

Je suis resté coincé ici. Je peux faire cela en utilisant des boucles for, etc. Mais je préfère le faire avec LINQ.

Yustme
la source
4
la recherche et la recherche sont deux choses différentes. Mais puisque vous pouvez regarder par-dessus mon épaule sur Internet, pouvez-vous me dire comment vous savez que je n'ai pas cherché? attendez ne dites pas! Vous l'avez bien vu? mon point exactement.
Yustme
5
poser une question coûte plus de temps qu'une recherche. la prochaine fois, supposez simplement qu'il / elle a fait une recherche ou 10.
Yustme
2
Cela retient encore un peu l'attention, alors j'ai pensé mentionner que ReSharper fait un très bon travail en suggérant des endroits où vous pouvez transformer le code itératif en instructions LINQ. Pour les nouveaux utilisateurs de LINQ, il peut être un outil indispensable à avoir à cette seule fin.
Yuck

Réponses:

206

Vous pouvez utiliser Contains()pour cela. Cela vous semblera un peu en arrière lorsque vous essayez vraiment de produire une INclause, mais cela devrait le faire:

var userProfiles = _dataContext.UserProfile
                               .Where(t => idList.Contains(t.Id));

Je suppose également que chaque UserProfileenregistrement aura un int Idchamp. Si ce n'est pas le cas, vous devrez vous adapter en conséquence.

Beurk
la source
Salut, oui, les enregistrements de profil utilisateur contiennent des identifiants. Donc je ferais quelque chose comme t => t.id == idList.Contains (id)?
Yustme
Contains()gérera cette vérification d'égalité sur chaque idvaleur si vous l'utilisez comme je l'ai écrit dans la réponse. Vous n'avez pas besoin d'écrire explicitement ==n'importe où lorsque vous essayez de comparer les éléments d'un ensemble (le tableau) à un autre (la table de base de données).
Beurk
Eh bien, le problème est que t contient tout l'objet de UserProfile, et l'idList ne contient que des int. Le compilateur s'est plaint de quelque chose mais j'ai réussi à le réparer. Merci.
Yustme
2
@Yuck - Ça ne marche pas pour moi, dit que la fonction a expiré! Ont désactivé le chargement paresseux mais échoue toujours.
bhuvin
1
J'obtiens "Impossible de convertir l'expression lambda en type 'int' car ce n'est pas un type délégué". Comment y remédier?
Stian
92

Solution avec .Where et .Contains a une complexité de O (N carré). Simple .Join devrait avoir de bien meilleures performances (proche de O (N) en raison du hachage). Le code correct est donc:

_dataContext.UserProfile.Join(idList, up => up.ID, id => id, (up, id) => up);

Et maintenant résultat de ma mesure. J'ai généré 100 000 profils utilisateur et 100 000 identifiants. Join a pris 32ms et .Where avec .Contains a pris 2 minutes et 19 secondes! J'ai utilisé IEnumerable pur pour ce test pour prouver ma déclaration. Si vous utilisez List au lieu de IEnumerable, .Where et .Contains seront plus rapides. Quoi qu'il en soit, la différence est significative. Le .Where .Contains le plus rapide est avec Set <>. Tout cela dépend de la complexité des couleurs sous-jacentes pour .Contains. Regardez cet article pour en savoir plus sur la complexité de linq.Regardez mon exemple de test ci-dessous:

    private static void Main(string[] args)
    {
        var userProfiles = GenerateUserProfiles();
        var idList = GenerateIds();
        var stopWatch = new Stopwatch();
        stopWatch.Start();
        userProfiles.Join(idList, up => up.ID, id => id, (up, id) => up).ToArray();
        Console.WriteLine("Elapsed .Join time: {0}", stopWatch.Elapsed);
        stopWatch.Restart();
        userProfiles.Where(up => idList.Contains(up.ID)).ToArray();
        Console.WriteLine("Elapsed .Where .Contains time: {0}", stopWatch.Elapsed);
        Console.ReadLine();
    }

    private static IEnumerable<int> GenerateIds()
    {
       // var result = new List<int>();
        for (int i = 100000; i > 0; i--)
        {
            yield return i;
        }
    }

    private static IEnumerable<UserProfile> GenerateUserProfiles()
    {
        for (int i = 0; i < 100000; i++)
        {
            yield return new UserProfile {ID = i};
        }
    }

Sortie de la console:

Temps écoulé. Rejoindre: 00: 00: 00.0322546

Temps écoulé .Où .Contient l'heure: 00: 02: 19.4072107

David Gregor
la source
4
Pouvez-vous étayer cela avec des chiffres?
Yustme
Nice, me rend cependant curieux de savoir quel serait le timing quand il Listest utilisé. +1
Yustme
Ok, voici les horaires qui vous intéressent: La liste a pris 13,1 secondes et HashSet a pris 0,7 ms! Ainsi, le .Where .Contains n'est meilleur que dans le cas de HashSet (lorsque .Contains a une complexité O (1)). Dans d'autres cas, le .Join est meilleur
David Gregor
5
Je reçois une Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.erreur lors de l' utilisation de datacontext LINQ2SQL.
Mayank Raichura
3
@Yustme - la performance est toujours une considération. (Je déteste être le gars "cela devrait être la réponse acceptée", mais ...)
jleach
19

Bonnes réponses, mais n'oubliez pas une chose IMPORTANTE - elles fournissent des résultats différents!

  var idList = new int[1, 2, 2, 2, 2]; // same user is selected 4 times
  var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e)).ToList();

Cela renverra 2 lignes de DB (et cela pourrait être correct, si vous voulez juste une liste triée distincte d'utilisateurs)

MAIS dans de nombreux cas, vous pourriez vouloir une liste de résultats non triés . Vous devez toujours y penser comme une requête SQL. Veuillez consulter l'exemple avec le panier d'achat d'eshop pour illustrer ce qui se passe:

  var priceListIDs = new int[1, 2, 2, 2, 2]; // user has bought 4 times item ID 2
  var shoppingCart = _dataContext.ShoppingCart
                     .Join(priceListIDs, sc => sc.PriceListID, pli => pli, (sc, pli) => sc)
                     .ToList();

Cela renverra 5 résultats de DB. L'utilisation de «contient» serait erronée dans ce cas.

Tomino
la source
13

Cela devrait être simple. Essaye ça:

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e));
Fabian Bigler
la source