Linq to Entities - Cláusula SQL "IN"

230

En T-SQL podría tener una consulta como:

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

¿Cómo replicaría eso en una consulta LINQ to Entities? ¿Es posible?

StevenMcD
fuente

Respuestas:

349

Debes darle la vuelta en términos de la forma en que piensas al respecto. En lugar de hacer "in" para encontrar los derechos de usuario del elemento actual en un conjunto predefinido de derechos de usuario aplicables, está solicitando un conjunto predefinido de derechos de usuario si contiene el valor aplicable del elemento actual. Esta es exactamente la misma forma en que encontraría un elemento en una lista regular en .NET.

Hay dos formas de hacerlo usando LINQ, una usa la sintaxis de consulta y la otra usa la sintaxis de método. Esencialmente, son iguales y podrían usarse indistintamente según su preferencia:

Sintaxis de consulta:

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

Método de sintaxis:

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

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

Mi preferencia personal en esta instancia podría ser la sintaxis del método porque en lugar de asignar la variable, podría hacer el foreach sobre una llamada anónima como esta:

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

Sintácticamente, esto parece más complejo, y hay que entender el concepto de expresiones lambda o delegados para entender realmente lo que está sucediendo, pero como puede ver, esto condensa el código bastante.

Todo se reduce a su estilo y preferencia de codificación: mis tres ejemplos hacen lo mismo de manera ligeramente diferente.

Una forma alternativa ni siquiera usa LINQ, puede usar la misma sintaxis de método reemplazando "where" con "FindAll" y obtener el mismo resultado, que también funcionará en .NET 2.0:

foreach(User u in users.FindAll(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}
BenAlabastro
fuente
tal vez fui demasiado rápido para marcar como respuesta, pero no recibo. Contiene después de que el {"Admin", "Usuario", "Limitado"} VS2008 no le gusta ese código ni un poco.
StevenMcD
1
fiel a mi nombre "FailBoy" lo descubrí: PI lo puso en una cadena [] y luego lo usé y funcionó. ¡Gracias!
StevenMcD
lo siento, olvidé actualizar la matriz anónima;) Arregle mi código de ejemplo. Aunque me alegro de que lo hayas descubierto por tu cuenta.
BenAlabaster
28
Esta respuesta habría sido correcta si la pregunta hubiera sido sobre Linq-to-SQL o Linq en general. Sin embargo, dado que dice específicamente "Linq-to-Entities", esta respuesta es incorrecta. array.Contains no es (todavía) compatible con Linq-to-Entities.
KristoferA
66
@KristoferA: eso puede haber sido cierto para versiones anteriores de EF, pero me parece bien con EF4.
Drew Noakes
21

Esto debería ser suficiente para su propósito. Compara dos colecciones y comprueba si una colección tiene los valores que coinciden con los de la otra colección.

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

Si está utilizando VS2008 / .net 3.5, consulte el consejo # 8 de Alex James: http://blogs.msdn.com/alexj/archive/2009/03/26/tip-8-writing-where-in-style -queries-using-linq-to-entity.aspx

De lo contrario, simplemente use el método array.Contains (someEntity.Member).

KristoferA
fuente
Evite usar respuestas de solo enlace, debe resumir el contenido del enlace en su respuesta, en caso de que el enlace se rompa en el futuro.
8

Iré por Inner Join en este contexto. Si hubiera usado contiene, iteraría 6 veces a pesar de que solo haya una coincidencia.

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

Desventajas de Contiene

Supongamos que tengo dos objetos de lista.

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

Usando Contiene, buscará cada elemento de la Lista 1 en la Lista 2, lo que significa que la iteración ocurrirá 49 veces.

Pankaj
fuente
55
Esto ignora por completo el hecho de que la declaración se traduce a SQL. Ver aquí .
Gert Arnold
5

Esta podría ser la forma posible de utilizar directamente los métodos de extensión LINQ para verificar la cláusula in

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

También intenté trabajar con algo parecido a SQL-IN: consultar un modelo de datos de entidad . Mi enfoque es un generador de cadenas para componer una gran expresión OR. Eso es terriblemente feo, pero me temo que es la única forma de hacerlo en este momento.

Ahora bien, eso se ve así:

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

Trabajar con GUID en este contexto : como puede ver arriba, siempre existe la palabra "GUID" antes del GUID si se encuentra en los fragmentos de la cadena de consulta. Si no agrega esto, ObjectQuery<T>.Wherelanza la siguiente excepción:

Los tipos de argumento 'Edm.Guid' y 'Edm.String' son incompatibles para esta operación., Casi igual a expresión, línea 6, columna 14.

Encontré esto en los foros de MSDN, podría ser útil tenerlo en cuenta.

Matías

... esperando la próxima versión de .NET y Entity Framework, cuando todo mejore. :)

Matthias Meid
fuente
2

Un método alternativo a la respuesta de BenAlabaster

En primer lugar, puede volver a escribir la consulta de esta manera:

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

Ciertamente, esto es más 'prolijo' y un dolor de escritura, pero funciona de todos modos.

Entonces, si tuviéramos algún método de utilidad que facilitara la creación de este tipo de expresiones LINQ estaríamos en el negocio.

Con un método de utilidad en su lugar, puede escribir algo como esto:

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

Esto genera una expresión que tiene el mismo efecto que:

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

Pero lo que es más importante en realidad funciona contra .NET 3.5 SP1.

Aquí está la función de fontanería que hace esto posible:

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

No voy a intentar explicar este método, aparte de decir que esencialmente construye una expresión de predicado para todos los valores usando valueSelector (es decir, p => p.User_Rights) y ORs esos predicados juntos para crear una expresión para el completo predicado

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

fuego en el hoyo
fuente
0

Ejemplo real:

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
fuente
-14

¿Seriamente? Ustedes nunca han usado

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)
cjm30305
fuente
99
-1 Intente esto con 20 o más valores en una tabla con más de 1000 filas y verá rápidamente la ventaja de la solución aceptada. Además, no es fácil agregar un número arbitrario de condiciones a la instrucción where (como si el usuario selecciona incluir las opciones 1 y 2, pero no 3).
Trisped
Bueno, no necesitaba nada del científico loco y esta respuesta fue mi voto porque necesitaba un AND y 2 ORS var SamplePoints = (de c en _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x => x.WSFStateCode) donde c. PWS == id && ((c.WSFStateCode.Substring (0, 2) == "SR") || (c.WSFStateCode.Substring (0, 2) == "CH")) seleccione c) .ToList () ;
JustJohn
@Trisped: el número de filas (1000) no cambia nada, ¿o me falta algo?
tymtam
@Tymski Sí, el número de filas es importante. Cuantas más filas, más cálculos. Lo mismo con el número de valores posibles: Checks = NumValues * NumRows. Debido a que este es un cálculo de tipo M * N, si cualquiera es pequeño, el tiempo para realizar cada verificación requerida también será pequeño. Agregué la restricción para que cjm30305 supiera cómo configurar un entorno de prueba que muestre por qué su solución es pobre.
Trisped
@Trisped ¿Estás diciendo que where new[] { 1, 2, 3 }.Contains(x)hace menos comparaciones entonces where (x == 1 || x == 2 || x == 3)?
tymtam