Linq do SQL, jak to zrobić „gdzie [kolumna] w (lista wartości)”

102

Mam funkcję, w której otrzymuję listę identyfikatorów i muszę zwrócić listę pasującą do opisu, który jest powiązany z identyfikatorem. Na przykład:

public class CodeData
{
    string CodeId {get; set;}
    string Description {get; set;}
}

public List<CodeData> GetCodeDescriptionList(List<string> codeIDs)
    //Given the list of institution codes, return a list of CodeData
    //having the given CodeIds
}

Więc gdybym sam tworzył sql dla tego, po prostu zrobiłbym coś podobnego do następującego (gdzie klauzula in zawiera wszystkie wartości w argumencie codeIds):

Select CodeId, Description FROM CodeTable WHERE CodeId IN ('1a','2b','3')

W Linq do Sql nie mogę znaleźć odpowiednika klauzuli „IN”. Najlepsze, jakie do tej pory znalazłem (które nie działa) to:

 var foo = from codeData in channel.AsQueryable<CodeData>()
           where codeData.CodeId == "1" || codeData.CodeId == "2"
           select codeData;

Problem polega na tym, że nie mogę dynamicznie wygenerować listy klauzul „OR” dla linq do sql, ponieważ są one ustawiane w czasie kompilacji.

Jak można osiągnąć klauzulę WHERE, która sprawdza, czy kolumna znajduje się na dynamicznej liście wartości przy użyciu Linq do Sql?

Nathan
źródło

Odpowiedzi:

161

Posługiwać się

where list.Contains(item.Property)

Lub w twoim przypadku:

var foo = from codeData in channel.AsQueryable<CodeData>()
          where codeIDs.Contains(codeData.CodeId)
          select codeData;

Ale równie dobrze możesz to zrobić w notacji kropkowej:

var foo = channel.AsQueryable<CodeData>()
                 .Where(codeData => codeIDs.Contains(codeData.CodeId));
Jon Skeet
źródło
jak używać w przypadku CodeId jest Integer?
Kiran Solkar
2
@KiranSolkar: Wtedy prawdopodobnie codeIDsbyłoby List<int>i wszystko byłoby w porządku.
Jon Skeet
@JonSkeet Czy to nie jest wielkość liter? Jeśli codeIDs jest listą ciągów wielkich liter, a codeData.codeId jest ciągiem małych liter, zakończy się niepowodzeniem.
PersyJack
@PersyJack: W pytaniu nie było nic o tym, żeby nie rozróżniać wielkości liter. Jeśli chodzi o to, czy tak będzie, nie pamiętam, czy LINQ to SQL domyślnie wymusza rozróżnianie wielkości liter, czy też pozwala zarządzać tym ustawieniom db.
Jon Skeet
1
@PersyJack LINQ to SQL generuje zapytanie T-SQL, które jest następnie uruchamiane na serwerze SQL przy użyciu ustawień bazy danych uwzględniających wielkość liter. Chociaż jeśli ktoś nie jest ostrożny i zmaterializuje wyniki zapytania, przed zastosowaniem LINQ do obiektów w pamięci, mogą one ponieść konsekwencje niezgodnej rozróżniania wielkości liter.
Zarepheth
26

Możesz również użyć:

List<int> codes = new List<int>();

codes.add(1);
codes.add(2);

var foo = from codeData in channel.AsQueryable<CodeData>()
          where codes.Any(code => codeData.CodeID.Equals(code))
          select codeData;
Nick DeMayo
źródło
1
Musiałem tego użyć, ponieważ nasza implementacja IQToolkit nie obsługuje .Contains ()
DJ van Wyk
1

Używałem metody w odpowiedzi Jona Skeeta, ale przyszła mi do głowy inna metoda Concat. ConcatMetoda wykonywana nieco lepiej w ograniczonej próbie, ale jest to uciążliwe i będę prawdopodobnie tylko trzymać Contains, a może będę napisać metodę pomocniczą, aby to zrobić dla mnie. Tak czy inaczej, oto inna opcja, jeśli ktoś jest zainteresowany:

Metoda

// Given an array of id's
var ids = new Guid[] { ... };

// and a DataContext
var dc = new MyDataContext();

// start the queryable
var query = (
    from thing in dc.Things
    where thing.Id == ids[ 0 ]
    select thing 
);

// then, for each other id
for( var i = 1; i < ids.Count(); i++ ) {
    // select that thing and concat to queryable
    query.Concat(
        from thing in dc.Things
        where thing.Id == ids[ i ]
        select thing
    );
}

Test wydajności

To nie było ani trochę naukowe. Wyobrażam sobie, że struktura bazy danych i liczba identyfikatorów znajdujących się na liście miałyby znaczący wpływ.

Skonfigurowałem test, w którym wykonałem 100 prób, Concata Containskażda próba obejmowała wybranie 25 wierszy określonych przez losową listę kluczy podstawowych. Uruchamiałem to kilkanaście razy i w większości przypadków Concatmetoda wychodzi 5 - 10% szybciej, chociaż raz Containswygrywała tylko odrobinę.

DCShannon
źródło
0
 var filterTransNos = (from so in db.SalesOrderDetails
                    where  ItemDescription.Contains(ItemDescription)
                            select new { so.TransNo }).AsEnumerable();    


listreceipt = listreceipt.Where(p => filterTransNos.Any(p2 => p2.TransNo == p.TransNo)).ToList();
Deepan Raj
źródło
0

Oto jak to robię za pomocą HashSet

        HashSet<String> hs = new HashSet<string>(new String[] { "Pluto", "Earth", "Neptune" });
        String[] arr =
        {
            "Pluto",
            "Earth",
            "Neptune",
            "Jupiter",
            "Saturn",
            "Mercury",
            "Pluto",
            "Earth",
            "Neptune",
            "Jupiter",
            "Saturn",
            "Mercury",
            // etc.
        };
        ICollection<String> coll = arr;

        String[] arrStrFiltered = coll.Where(str => hs.Contains(str)).ToArray();

HashSet jest w zasadzie prawie do O (1), więc twoja złożoność pozostaje O (n).

MG
źródło
Chodzi o LINQ-to-SQL. Takie zagadnienia dotyczące LINQ-to-objects nie mają zastosowania.
Gert Arnold
Kolekcja ICollection może również pochodzić z LINQ-SQL, jest to sposób ogólny
MG
Pytanie dotyczy tego, jak zbudować wyrażenie, które przekłada się na poprawny SQL. Nie ma to nic wspólnego z przeszukiwaniem lokalnej kolekcji. Twoja odpowiedź tylko zmyli przyszłych czytelników, którzy nie są świadomi tego rozróżnienia.
Gert Arnold