Linq to Entities - klauzula SQL „IN”


230

W T-SQL możesz mieć zapytanie takie jak:

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

Jak zreplikowałbyś to w zapytaniu LINQ to Entities? Czy to w ogóle możliwe?

Odpowiedzi:


349

Musisz obrócić go na głowie pod względem sposobu, w jaki myślisz o tym. Zamiast robić „in”, aby znaleźć prawa użytkownika dla bieżącego elementu w predefiniowanym zestawie odpowiednich praw użytkownika, pytasz o predefiniowany zestaw praw użytkownika, jeśli zawiera on odpowiednią wartość dla bieżącego elementu. W ten sam sposób można znaleźć element na zwykłej liście w .NET.

Można to zrobić na dwa sposoby za pomocą LINQ, jeden używa składni zapytania, a drugi - metody. Zasadniczo są one takie same i mogą być używane zamiennie w zależności od preferencji:

Składnia zapytania:

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

Składnia metody:

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

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

Moją osobistą preferencją w tym przypadku może być składnia metody, ponieważ zamiast przypisywać zmienną, mógłbym wykonać foreach za pomocą anonimowego wywołania, takiego jak to:

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

Składniowo wygląda to na bardziej złożone i musisz zrozumieć pojęcie wyrażeń lambda lub delegatów, aby naprawdę zorientować się, co się dzieje, ale jak widać, skraca to sporo kod.

Wszystko sprowadza się do twojego stylu kodowania i preferencji - wszystkie trzy moje przykłady robią to samo nieco inaczej.

Alternatywny sposób nawet nie korzysta z LINQ, możesz użyć tej samej metody, zastępując „gdzie” słowem „FindAll” i uzyskać ten sam wynik, który będzie również działał w .NET 2.0:

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

może byłem zbyt szybki, aby zaznaczyć jako odpowiedź, ale nie dostaję .Zawiera po tym, jak {„Administrator”, „Użytkownik”, „Ograniczony”} VS2008 nie podoba się ten kod ani trochę.
StevenMcD

1
zgodnie z moim imieniem „FailBoy” wymyśliłem: PI wstawiłem do łańcucha [], a potem użyłem go i zadziałało. Dzięki!
StevenMcD

przepraszam, zapomniałem zaktualizować tablicę anonimową;) Naprawiłem mój przykład kodu. Cieszę się, że sam to wymyśliłeś.
BenAlabaster

28
Ta odpowiedź byłaby poprawna, gdyby pytanie dotyczyło Linq-SQL lub ogólnie Linq. Ponieważ jednak wyraźnie mówi „Linq-to-Entities”, ta odpowiedź jest niepoprawna. array.Contains nie jest (jeszcze) obsługiwany przez Linq-to-Entities.
KristoferA

6
@KristoferA - to może być prawda dla wcześniejszych wersji EF, ale wydaje mi się, że z EF4 jest w porządku.
Drew Noakes

21

To powinno wystarczyć do twojego celu. Porównuje dwie kolekcje i sprawdza, czy jedna kolekcja ma wartości pasujące do wartości w drugiej kolekcji

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

9

Jeśli używasz VS2008 / .net 3.5, zobacz wskazówkę Alexa Jamesa nr 8: http://blogs.msdn.com/alexj/archive/2009/03/26/tip-8-writing-where-in-style -queries-using-linq-to-entity.aspx

W przeciwnym razie wystarczy użyć metody array.Contains (someEntity.Member).


Należy unikać korzystania z odpowiedzi tylko do linków, w podsumowaniu należy streścić treść linku, na wypadek, gdyby w przyszłości nastąpił zerwanie linku.


8

W tym kontekście wybiorę opcję dołączenia wewnętrznego. Gdybym użył zawierał, iterowałby 6 razy, mimo że fakt, że jest tylko jedno dopasowanie.

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

Wady zawartości

Załóżmy, że mam dwa obiekty listy.

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

Za pomocą Contains wyszuka każdą pozycję z Listy 1 na Liście 2, co oznacza, że ​​iteracja nastąpi 49 razy !!!


5
To całkowicie ignoruje fakt, że instrukcja jest tłumaczona na SQL. Zobacz tutaj .
Gert Arnold,

5

Może to być możliwy sposób bezpośredniego użycia metod rozszerzenia LINQ w celu sprawdzenia klauzuli in

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

2

Próbowałem także pracować z rzeczą podobną do SQL-IN - zapytania do modelu danych jednostki . Moje podejście polega na tworzeniu ciągów znaków w celu skomponowania dużego wyrażenia OR. To okropnie brzydkie, ale obawiam się, że teraz jest to jedyna droga.

Teraz wygląda to tak:

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

Praca z identyfikatorami GUID w tym kontekście : jak widać powyżej, zawsze we fragmentach ciągu zapytania zawsze znajduje się słowo „GUID”. Jeśli tego nie dodasz, ObjectQuery<T>.Wherezgłasza następujący wyjątek:

Typy argumentów „Edm.Guid” i „Edm.String” są niezgodne z tą operacją., Prawie równa wyrażeniu, wiersz 6, kolumna 14.

Znalazłem to na forach MSDN, warto mieć to na uwadze.

Matthias

... czekamy na kolejną wersję .NET i Entity Framework, kiedy wszystko będzie lepiej. :)


2

Alternatywna metoda dla odpowiedzi BenAlabaster

Przede wszystkim możesz przepisać zapytanie w następujący sposób:

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

Z pewnością jest to bardziej „niewygodne” i trudne do napisania, ale działa tak samo.

Gdybyśmy mieli jakąś użyteczną metodę, która ułatwiłaby tworzenie tego rodzaju wyrażeń LINQ, bylibyśmy w biznesie.

za pomocą metody narzędziowej możesz napisać coś takiego:

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

To buduje wyrażenie, które ma taki sam efekt jak:

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

Ale co ważniejsze, faktycznie działa przeciwko .NET 3.5 SP1.

Oto funkcja hydrauliczna, która umożliwia:

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

Nie zamierzam wyjaśniać tej metody, poza tym, że zasadniczo buduje wyrażenie predykatu dla wszystkich wartości za pomocą valueSelector (tj. P => p.User_Rights) i OR tych predykatów razem, aby utworzyć wyrażenie dla kompletności orzec

Źródło: http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx


0

Prawdziwy przykład:

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;

-13

Poważnie? Wy nigdy nie korzystaliście

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

9
-1 Wypróbuj to z co najmniej 20 wartościami w tabeli zawierającej ponad 1000 wierszy, a szybko zobaczysz zaletę przyjętego rozwiązania. Ponadto nie jest łatwo dodać dowolną liczbę warunków do instrukcji where (na przykład, jeśli użytkownik zdecyduje się dołączyć opcje 1 i 2, ale nie 3).
Trisped

Cóż, nie potrzebowałem żadnych szalonych naukowców i ta odpowiedź trafiła do mojego głosu, ponieważ potrzebowałem AND i 2 ORS var SamplePoints = (od c w _db.tblPWS_WSF_SPID_ISN_Lookup.OrderBy (x => x.WSFStateCode) gdzie c. PWS == id && ((c.WSFStateCode.Substring (0, 2) == "SR") || (c.WSFStateCode.Substring (0, 2) == "CH")) wybierz c) .ToList () ;
JustJohn

@Trisped - liczba wierszy (1000) nic nie zmienia - czy coś mi brakuje?
tymtam

@Tymski Tak, liczba rzędów ma znaczenie. Im więcej wierszy, tym więcej obliczeń. Podobnie jest z liczbą możliwych wartości: Checks = NumValues * NumRows. Ponieważ jest to obliczenie typu M * N, jeśli jedno z nich jest małe, czas wykonania każdej wymaganej kontroli również będzie krótki. Dodałem ograniczenie, aby cjm30305 wiedział, jak skonfigurować środowisko testowe, w którym pokaże, dlaczego jego rozwiązanie jest słabe.
Trisped 18.09.16

@Trisped Czy mówisz, że where new[] { 1, 2, 3 }.Contains(x)wtedy mniej porównań where (x == 1 || x == 2 || x == 3)?
tymtam
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.