wake-up-neo.com

Linq to Entities - SQL "IN" -Klausel

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?

223
StevenMcD

Sie müssen es auf den Kopf stellen, wenn Sie darüber nachdenken. Anstatt "in" zu tun, um die Benutzerrechte des aktuellen Elements in einem vordefinierten Satz von anwendbaren Benutzerrechten zu suchen, 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: Eine verwendet Abfragesyntax und die andere Methodensyntax. Im Wesentlichen sind sie identisch und können je nach Vorliebe 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-Befehl über einen anonymen Aufruf 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 von Lambda-Ausdrücken oder -Delegierten verstehen, um wirklich herauszufinden, was vor sich geht.

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

Eine alternative Methode verwendet nicht einmal LINQ. Sie können dieselbe Methodensyntax verwenden, indem Sie "where" durch "FindAll" ersetzen und dasselbe Ergebnis erzielen, 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;
}
342
BenAlabaster

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

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

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

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

9
KristoferA

Ich werde mich in diesem Zusammenhang für Inner Join entscheiden. Wenn ich Enthält verwendet hätte, würde es trotz der Tatsache, dass es nur eine Übereinstimmung gibt, 6-mal iterieren.

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 von Liste 1 in Liste 2 gesucht, was bedeutet, dass die Iteration 49 Mal erfolgt !!!

7
Pankaj

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

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;

Das ist zwar wortreicher und schmerzhafter zu schreiben, aber es funktioniert trotzdem.

Wenn wir also eine Dienstprogrammmethode hätten, die das Erstellen dieser Art von LINQ-Ausdrücken vereinfacht, wären wir im Geschäft.

mit einer vorhandenen Utility-Methode können Sie Folgendes schreiben:

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

Dies erzeugt einen Ausdruck, 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 mit dem valueSelector erstellt (dh p => p.User_Rights) 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

2

Ich habe auch versucht, mit einer SQL-IN-ähnlichen Sache zu arbeiten - eine Abfrage gegen ein Entity Data Model. Mein Ansatz ist ein Stringbuilder, um einen großen OR-Ausdruck zu erstellen. Das ist furchtbar hässlich, aber ich fürchte, es ist der einzige Weg, um jetzt zu gehen.

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 Kontext: Wie Sie oben sehen können, steht in den Fragmenten der Abfragezeichenfolge immer das Wort "GUID" vor dem GUID ifself. Wenn Sie don nicht hinzufügen, ObjectQuery<T>.Where löst die folgende Ausnahme aus:

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

Gefunden dies in MSDN-Foren, könnte hilfreich sein, im Auge zu behalten.

Matthias

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

2
Matthias Meid

Reales 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;
0
Adel Mourad