Linq to Entities - clausola SQL "IN"


230

In T-SQL potresti avere una query come:

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

Come lo replicheresti in una query LINQ to Entities? È anche possibile?

Risposte:


349

Devi capovolgerlo in termini di modo in cui ci stai pensando. Invece di fare "in" per trovare i diritti utente dell'articolo corrente in un set predefinito di diritti utente applicabili, stai chiedendo a un set predefinito di diritti utente se contiene il valore applicabile dell'articolo corrente. Questo è esattamente lo stesso modo in cui troveresti un elemento in un normale elenco in .NET.

Esistono due modi per eseguire questa operazione utilizzando LINQ, uno utilizza la sintassi della query e l'altro utilizza la sintassi del metodo. Essenzialmente, sono uguali e potrebbero essere usati in modo intercambiabile a seconda delle tue preferenze:

Sintassi della query:

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

Sintassi del metodo:

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

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

La mia preferenza personale in questo caso potrebbe essere la sintassi del metodo perché invece di assegnare la variabile, potrei fare la foreach su una chiamata anonima come questa:

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

Sintatticamente questo sembra più complesso, e devi capire il concetto di espressioni lambda o delegati per capire davvero cosa sta succedendo, ma come puoi vedere, questo condensa il codice un discreto importo.

Tutto dipende dal tuo stile di codifica e dalle tue preferenze: tutti e tre i miei esempi fanno la stessa cosa in modo leggermente diverso.

Un modo alternativo non usa nemmeno LINQ, puoi usare la stessa sintassi del metodo sostituendo "where" con "FindAll" e ottenere lo stesso risultato, che funzionerà anche in .NET 2.0:

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

forse sono stato troppo veloce per contrassegnare come risposta, ma non ottengo un .Contains dopo il {"Admin", "User", "Limited"} VS2008 non mi piace un po 'quel codice.
StevenMcD,

1
fedele al mio nome "FailBoy" l'ho capito: PI ha inserito una stringa [] e poi l'ha usato e ha funzionato. Grazie!
StevenMcD,

scusate, ho dimenticato di rinnovare l'array anonimo;) Ho corretto il mio esempio di codice. Sono contento che tu l'abbia capito da solo però.
BenAlabaster,

28
Questa risposta sarebbe stata corretta se la domanda riguardasse Linq-to-SQL o Linq in generale. Tuttavia, poiché dice specificamente "Linq-to-Entities", questa risposta non è corretta. array.Contains non è (ancora) supportato da Linq-to-Entities.
Kristofer,

6
@KristoferA - questo potrebbe essere vero per le versioni precedenti di EF, ma per me sembra perfetto con EF4.
Drew Noakes

21

Questo dovrebbe bastare al tuo scopo. Confronta due raccolte e verifica se una raccolta ha i valori corrispondenti a quelli dell'altra raccolta

fea_Features.Where(s => selectedFeatures.Contains(s.feaId))

9

Se stai usando VS2008 / .net 3.5, vedi il consiglio n. 8 di Alex James: http://blogs.msdn.com/alexj/archive/2009/03/26/tip-8-writing-where-in-style -queries-con-LINQ-to-entities.aspx

Altrimenti basta usare il metodo array.Contains (someEntity.Member).


Evitare di utilizzare le risposte solo al collegamento, è necessario riepilogare i contenuti del collegamento nella risposta, nel caso in cui il collegamento si interrompa in futuro.


8

Proverò Inner Join in questo contesto. Se avessi usato contiene, ripeterebbe 6 volte nonostante il fatto che ci sia una sola corrispondenza.

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

Svantaggi di Contiene

Supponiamo di avere due oggetti elenco.

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

Usando Contiene, cercherà ogni elemento della Lista 1 nella Lista 2, il che significa che l'iterazione avverrà 49 volte !!!


5
Ciò ignora completamente il fatto che l'istruzione sia tradotta in SQL. Vedi qui .
Gert Arnold,

5

Questo potrebbe essere il modo possibile in cui è possibile utilizzare direttamente i metodi di estensione LINQ per verificare la clausola in

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

2

Ho anche provato a lavorare con una cosa simile a SQL-IN: interrogare un Entity Data Model . Il mio approccio è un generatore di stringhe per comporre una grande espressione OR. È terribilmente brutto, ma temo che sia l'unico modo per andare adesso.

Bene, sembra così:

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

Lavorare con i GUID in questo contesto : come puoi vedere sopra, c'è sempre la parola "GUID" prima del GUID se stesso nei frammenti della stringa di query. Se non lo aggiungi, ObjectQuery<T>.Wheregenera la seguente eccezione:

I tipi di argomento 'Edm.Guid' e 'Edm.String' non sono compatibili con questa operazione., Quasi uguale a espressione, riga 6, colonna 14.

Trovato questo nei forum MSDN, potrebbe essere utile tenere a mente.

Matthias

... in attesa della prossima versione di .NET ed Entity Framework, quando tutto andrà meglio. :)


2

Un metodo alternativo alla risposta di BenAlabaster

Prima di tutto, puoi riscrivere la query in questo modo:

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

Certamente questo è più "prolisso" e un dolore da scrivere ma funziona lo stesso.

Quindi, se avessimo un metodo di utilità che facilitasse la creazione di questo tipo di espressioni LINQ, saremmo in affari.

con un metodo di utilità in atto puoi scrivere qualcosa del genere:

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

Questo crea un'espressione che ha lo stesso effetto di:

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

Ma cosa più importante in realtà funziona con .NET 3.5 SP1.

Ecco la funzione idraulica che lo rende possibile:

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

Non proverò a spiegare questo metodo, oltre a dire che essenzialmente crea un'espressione predicata per tutti i valori usando valueSelector (cioè p => p.User_Rights) e OR quei predicati insieme per creare un'espressione per il completo predicato

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


0

Vero esempio:

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;

-14

Sul serio? Non avete mai usato

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)

9
-1 Prova con 20 o più valori in una tabella con oltre 1000 righe e vedrai rapidamente il vantaggio della soluzione accettata. Inoltre, non è facile aggiungere un numero arbitrario di condizioni all'istruzione where (come se l'utente stesse selezionando di includere le opzioni 1 e 2, ma non 3).
Trisped

Bene, non avevo bisogno di nessuna roba da scienziato pazzo e questa risposta va al mio voto perché avevo bisogno di un AND e 2 ORS var SamplePoints = (da c in _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x => x.WSFStateCode) dove c. PWS == id && ((c.WSFStateCode.Substring (0, 2) == "SR") || (c.WSFStateCode.Substring (0, 2) == "CH")) seleziona c) .ToList () ;
JustJohn,

@Trisped - il numero di righe (1000) non cambia nulla - o mi manca qualcosa?
tymtam,

@Tymski Sì, il numero di righe è importante. Più righe, più calcoli. Lo stesso con il numero di valori possibili:Checks = NumValues * NumRows . Poiché si tratta di un calcolo del tipo M * N, se uno dei due è piccolo, anche il tempo per eseguire ciascun controllo richiesto sarà ridotto. Ho aggiunto il vincolo in modo che cjm30305 sappia come impostare un ambiente di test in cui mostrare perché la sua soluzione è scadente.
Trisped

@Trisped Lo stai dicendo where new[] { 1, 2, 3 }.Contains(x) fa meno confronti allora where (x == 1 || x == 2 || x == 3)?
tymtam,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.