Linq to Entities - SQL-Klausel „IN“

230

In T-SQL könnten Sie eine Abfrage haben wie:

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

Wie würden Sie das in einer LINQ to Entities-Abfrage replizieren? Ist es überhaupt möglich?

StevenMcD
quelle

Antworten:

349

Sie müssen es in Bezug auf die Art und Weise, wie Sie darüber denken, auf den Kopf stellen. Anstatt "in" zu tun, um die Benutzerrechte des aktuellen Elements in einem vordefinierten Satz anwendbarer Benutzerrechte zu finden, fragen Sie einen vordefinierten Satz von Benutzerrechten, ob er den anwendbaren Wert des aktuellen Elements enthält. Dies ist genau die gleiche Art und Weise, wie Sie ein Element in einer regulären Liste in .NET finden würden.

Es gibt zwei Möglichkeiten, dies mit LINQ zu tun: Die eine verwendet die Abfragesyntax und die andere die Methodensyntax. Im Wesentlichen sind sie gleich und können je nach Ihren Vorlieben austauschbar verwendet werden:

Abfragesyntax:

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

Methodensyntax:

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

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

Meine persönliche Präferenz in diesem Fall könnte die Methodensyntax sein, da ich anstelle der Zuweisung der Variablen den foreach über einen anonymen Aufruf wie diesen ausführen könnte:

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

Syntaktisch sieht dies komplexer aus, und Sie müssen das Konzept der Lambda-Ausdrücke oder -Delegierten verstehen, um wirklich herauszufinden, was vor sich geht. Wie Sie jedoch sehen können, wird der Code dadurch erheblich komprimiert.

Alles hängt von Ihrem Codierungsstil und Ihren Vorlieben ab - alle drei meiner Beispiele machen dasselbe etwas anders.

Eine alternative Methode verwendet nicht einmal LINQ. Sie können dieselbe Methodensyntax verwenden, indem Sie "where" durch "FindAll" ersetzen und dasselbe Ergebnis erhalten, das auch in .NET 2.0 funktioniert:

foreach(User u in users.FindAll(u => new [] { "Admin", "User", "Limited" }.Contains(u.User_Rights)))
{
    //Do stuff on each selected user;
}
BenAlabaster
quelle
Vielleicht war ich zu schnell, um als Antwort zu markieren, aber ich bekomme keine. Enthält nach dem {"Admin", "User", "Limited"} VS2008 mag diesen Code kein bisschen.
StevenMcD
1
Getreu meinem Namen "FailBoy" habe ich es herausgefunden: PI in einen String [] eingefügt und dann verwendet und es hat funktioniert. Vielen Dank!
StevenMcD
Entschuldigung, ich habe vergessen, das anonyme Array neu zu erstellen;) Ich habe mein Codebeispiel korrigiert. Ich bin froh, dass du es selbst herausgefunden hast.
BenAlabaster
28
Diese Antwort wäre richtig gewesen, wenn die Frage Linq-to-SQL oder Linq im Allgemeinen gewesen wäre. Da jedoch ausdrücklich "Linq-to-Entities" steht, ist diese Antwort falsch. array.Contains wird (noch) nicht von Linq-to-Entities unterstützt.
KristoferA
6
@KristoferA - das mag für frühere Versionen von EF zutreffen, aber mit EF4 scheint es mir in Ordnung zu sein.
Drew Noakes
21

Dies sollte Ihrem Zweck genügen. Es vergleicht zwei Sammlungen und prüft, ob für eine Sammlung die Werte mit denen in der anderen Sammlung übereinstimmen

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

Wenn Sie VS2008 / .net 3.5 verwenden, lesen Sie Alex James 'Tipp Nr. 8: http://blogs.msdn.com/alexj/archive/2009/03/26/tip-8-writing-where-in-style -queries-using-linq-to-entity.aspx

Verwenden Sie andernfalls einfach die Methode array.Contains (someEntity.Member).

KristoferA
quelle
Bitte vermeiden Sie die Verwendung von Nur-Link-Antworten. Sie sollten den Inhalt des Links in Ihrer Antwort zusammenfassen, falls der Link in Zukunft unterbrochen wird.
8

Ich werde mich in diesem Zusammenhang für Inner Join entscheiden. Wenn ich enthält verwendet hätte, würde es 6 Mal iterieren, obwohl es nur eine Übereinstimmung gibt.

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

Nachteile von Enthält

Angenommen, ich habe zwei Listenobjekte.

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

Mit Contains wird nach jedem Element der Liste 1 in Liste 2 gesucht, was bedeutet, dass die Iteration 49 Mal erfolgt !!!

Pankaj
quelle
5
Dies ignoriert völlig die Tatsache, dass die Anweisung in SQL übersetzt wird. Siehe hier .
Gert Arnold
5

Auf diese Weise können Sie die in-Klausel direkt mit LINQ-Erweiterungsmethoden überprüfen

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

Ich habe auch versucht, mit einer SQL-IN-ähnlichen Sache zu arbeiten - Abfragen gegen ein Entitätsdatenmodell . Mein Ansatz ist ein String-Builder, um einen großen ODER-Ausdruck zu erstellen. Das ist furchtbar hässlich, aber ich fürchte, es ist der einzige Weg, den wir jetzt gehen können.

Nun gut, das sieht so aus:

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

Arbeiten mit GUIDs in diesem Zusammenhang : Wie Sie oben sehen können, steht in den Abfragezeichenfolgenfragmenten immer das Wort "GUID" vor der GUID. Wenn Sie dies nicht hinzufügen, wird ObjectQuery<T>.Wheredie folgende Ausnahme ausgelöst:

Die Argumenttypen 'Edm.Guid' und 'Edm.String' sind für diese Operation nicht kompatibel. Nahezu gleich Ausdruck, Zeile 6, Spalte 14.

Fand dies in MSDN-Foren, könnte hilfreich sein, daran zu denken.

Matthias

... freuen uns auf die nächste Version von .NET und Entity Framework, wenn alles besser wird. :) :)

Matthias Meid
quelle
2

Eine alternative Methode zur BenAlabaster-Antwort

Zunächst können Sie die Abfrage folgendermaßen umschreiben:

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

Sicherlich ist dies "wortreicher" und ein Schmerz zu schreiben, aber es funktioniert trotzdem.

Wenn wir also eine Dienstprogrammmethode hätten, die es einfach macht, diese Art von LINQ-Ausdrücken zu erstellen, wären wir im Geschäft.

Mit einer vorhandenen Dienstprogrammmethode können Sie Folgendes schreiben:

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

Dadurch wird ein Ausdruck erstellt, der den gleichen Effekt hat wie:

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

Aber was noch wichtiger ist, funktioniert tatsächlich gegen .NET 3.5 SP1.

Hier ist die Sanitärfunktion, die dies ermöglicht:

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

Ich werde nicht versuchen, diese Methode zu erklären, außer zu sagen, dass sie im Wesentlichen einen Prädikatausdruck für alle Werte unter Verwendung des valueSelector (dh p => p.User_Rights) erstellt und diese Prädikate zusammen ODER verknüpft, um einen Ausdruck für das Ganze zu erstellen Prädikat

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

Feuer im Loch
quelle
0

Echtes Beispiel:

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
quelle
-13

Ernsthaft? Ihr Leute habt noch nie benutzt

where (t.MyTableId == 1 || t.MyTableId == 2 || t.MyTableId == 3)
cjm30305
quelle
9
-1 Versuchen Sie dies mit 20 oder mehr Werten in einer Tabelle mit über 1000 Zeilen, und Sie werden schnell den Vorteil der akzeptierten Lösung erkennen. Es ist auch nicht einfach, der where-Anweisung eine beliebige Anzahl von Bedingungen hinzuzufügen (z. B. wenn der Benutzer Option 1 und 2 einschließt, aber nicht 3).
Trisped
Nun, ich brauchte nichts von den verrückten Wissenschaftlern und diese Antwort ging meiner Stimme nach, weil ich ein AND und 2 ORS var SamplePoints = (von c in _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x => x.WSFStateCode) brauchte, wobei c. PWS == id && ((c.WSFStateCode.Substring (0, 2) == "SR") || (c.WSFStateCode.Substring (0, 2) == "CH")) Wählen Sie c) .ToList () ;;
JustJohn
@Trisped - die Anzahl der Zeilen (1000) ändert nichts - oder fehlt mir etwas?
Tymtam
@Tymski Ja, die Anzahl der Zeilen ist wichtig. Je mehr Zeilen, desto mehr Berechnungen. Gleiches gilt für die Anzahl der möglichen Werte : Checks = NumValues * NumRows. Da es sich um eine Berechnung vom Typ M * N handelt, ist auch die Zeit für die Durchführung jeder erforderlichen Prüfung gering, wenn eine der beiden Berechnungen klein ist. Ich habe die Einschränkung hinzugefügt, damit cjm30305 weiß, wie eine Testumgebung eingerichtet wird, in der gezeigt wird, warum seine Lösung schlecht ist.
Trisped
@Trisped Wollen Sie damit sagen, dass dann where new[] { 1, 2, 3 }.Contains(x)weniger Vergleiche möglich sind where (x == 1 || x == 2 || x == 3)?
Tymtam