Seleccione varios registros basados ​​en la lista de Id con linq

122

Tengo una lista que contiene las identificaciones de mi UserProfilemesa. ¿Cómo puedo seleccionar todo en UserProfilesfunción de la lista de identificaciones que obtuve en unvar uso LINQ?

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

Me quedé atrapado aquí mismo. Puedo hacer esto usando bucles for, etc. Pero prefiero hacer esto conLINQ .

Yustme
fuente
4
buscar y encontrar son dos cosas diferentes. Pero como puedes mirar por encima de mi hombro a través de Internet, ¿podrías decirme cómo sabes que no busqué? espera no lo digas! ¿Lo viste bien? exactamente mi punto.
Yustme
5
hacer una pregunta cuesta más tiempo que hacer una búsqueda. la próxima vez simplemente asuma que 'él / ella' hizo una búsqueda o 10.
Yustme
2
Esto todavía recibe bastante atención, por lo que pensé en mencionar que ReSharper hace un muy buen trabajo sugiriendo lugares donde podría convertir código iterativo en declaraciones LINQ. Para las personas nuevas en LINQ, puede ser una herramienta indispensable tener solo para este propósito.
Yuck

Respuestas:

206

Puedes usar Contains()para eso. Se sentirá un poco al revés cuando realmente esté tratando de producir una INcláusula, pero esto debería ser suficiente:

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

También supongo que cada UserProfileregistro tendrá un int Idcampo. Si ese no es el caso, tendrá que ajustar en consecuencia.

¡Qué asco!
fuente
Hola, sí, los registros de perfil de usuario contienen identificaciones. Entonces, de alguna manera, estaría haciendo algo como t => t.id == idList.Contains (id)?
Yustme
Contains()manejará esa verificación de igualdad en cada idvalor si lo usa como lo he escrito en la respuesta. No tiene que escribir explícitamente en ==ningún lugar cuando intenta comparar los elementos de un conjunto (la matriz) con otro (la tabla de la base de datos).
Yuck
Bueno, el problema es que contiene todo el objeto de UserProfile, y idList solo contiene int. El compilador se quejó de algo pero me las arreglé para solucionarlo. Gracias.
Yustme
2
@Yuck - ¡No me funciona, dice que se agotó el tiempo de espera de la función! Ha deshabilitado la carga diferida pero aún falla.
bhuvin
1
Me sale "No se puede convertir la expresión lambda al tipo 'int' porque no es un tipo delegado". ¿Cómo arreglar eso?
Stian
92

La solución con .Where y .Contains tiene una complejidad de O (N cuadrado). Simple .Join debería tener un rendimiento mucho mejor (cerca de O (N) debido al hash). Entonces el código correcto es:

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

Y ahora resultado de mi medida. Genere 100 000 UserProfiles y 100 000 ID. Join tomó 32ms y .Where with .Contains tomó 2 minutos y 19 segundos. Usé IEnumerable puro para esta prueba para probar mi declaración. Si usa List en lugar de IEnumerable, .Where y .Contains serán más rápidos. De todos modos la diferencia es significativa. El .Donde .Contains más rápido es con Set <>. Todo depende de la complejidad de las colecciones subyacentes para .Contains. Mira esta publicación para aprender sobre la complejidad de linq. Mira mi ejemplo de prueba a continuación:

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

Salida de consola:

Transcurrido .Tiempo de unión: 00: 00: 00.0322546

Transcurrido .Donde .Contiene tiempo: 00: 02: 19.4072107

David Gregor
fuente
4
¿Puedes respaldar eso con números?
Yustme
Agradable, sin embargo, me da curiosidad saber cuáles serían los tiempos cuando Listse usa. +1
Yustme
Ok, aquí están los tiempos que le interesan: ¡La lista tomó 13.1 segundos y HashSet tomó 0,7 ms! Entonces, .Where .Contains es mejor solo en el caso de HashSet (cuando .Contains tiene complejidad O (1)). En otros casos, .Join es mejor
David Gregor
5
Recibo un Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.error al usar el contexto de datos LINQ2SQL.
Mayank Raichura
3
@Yustme: el rendimiento siempre es una consideración. (Odio ser el tipo de "esta debería ser la respuesta aceptada", pero ...)
jleach
19

Buenas respuestas a continuación, pero no olvide una cosa IMPORTANTE : ¡brindan resultados diferentes!

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

Esto devolverá 2 filas de DB (y esto podría ser correcto, si solo desea una lista ordenada de usuarios distinta)

PERO en muchos casos, es posible que desee una lista de resultados sin clasificar . Siempre tienes que pensar en ello como en una consulta SQL. Consulte el ejemplo con el carrito de compras de eshop para ilustrar lo que está sucediendo:

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

Esto devolverá 5 resultados de DB. Usar 'contiene' sería incorrecto en este caso.

Tomino
fuente
13

Eso debería ser simple. Prueba esto:

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e));
Fabián Bigler
fuente