Linq to SQL, как сделать «где [столбец] в (список значений)»

102

У меня есть функция, в которой я получаю список идентификаторов, и мне нужно вернуть список, соответствующий описанию, связанному с идентификатором. Например:

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
}

Поэтому, если бы я сам создавал sql для этого, я бы просто сделал что-то вроде следующего (где предложение in содержит все значения в аргументе codeIds):

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

В Linq to Sql я не могу найти эквивалент предложения «IN». Лучшее, что я нашел до сих пор (что не работает):

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

Проблема в том, что я не могу динамически генерировать список предложений «ИЛИ» для linq to sql, потому что они устанавливаются во время компиляции.

Как выполнить предложение where, которое проверяет наличие столбца в динамическом списке значений с помощью Linq to Sql?

Натан
источник

Ответы:

161

Использовать

where list.Contains(item.Property)

Или в вашем случае:

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

Но вы также можете сделать это в точечной нотации:

var foo = channel.AsQueryable<CodeData>()
                 .Where(codeData => codeIDs.Contains(codeData.CodeId));
Джон Скит
источник
как использовать, если CodeId является целым числом ??
Kiran Solkar 03
2
@KiranSolkar: Тогда, по-видимому, codeIDsбудет List<int>, и все будет хорошо.
Джон Скит,
@JonSkeet Разве это не чувствительно к регистру? Если codeIDs - это список строк в верхнем регистре, а codeData.codeId - строка в нижнем регистре, это не удастся.
PersyJack
@PersyJack: В вопросе не было ничего о том, что он должен быть нечувствительным к регистру. Что касается того, будет ли это так, я не могу вспомнить, обеспечивает ли LINQ to SQL чувствительность к регистру по умолчанию или позволяет настройкам db управлять этим.
Джон Скит
1
@PersyJack LINQ to SQL генерирует запрос T-SQL, который затем выполняется на SQL Server с использованием настроек базы данных с учетом регистра. Хотя, если кто-то не будет осторожен и материализует результаты запроса, перед применением LINQ к объектам в памяти они могут пострадать от последствий несоответствия чувствительности к регистру.
Zarepheth
26

Вы также можете использовать:

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;
Ник ДеМайо
источник
1
Мне пришлось использовать это, поскольку наша реализация IQToolkit не поддерживает .Contains ()
DJ van Wyk
1

Я использовал метод в ответе Джона Скита, но мне пришло в голову другое использование Concat. Этот Concatметод показал себя немного лучше в ограниченном тесте, но это хлопотно, и я, вероятно, просто буду придерживаться его Containsили, может быть, я напишу вспомогательный метод, чтобы сделать это за меня. В любом случае, вот еще один вариант, если кому-то интересно:

Метод

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

Тест производительности

Это не было даже отдаленно научным. Я полагаю, что ваша структура базы данных и количество идентификаторов, включенных в список, окажут значительное влияние.

Я установил тест, в котором я провел по 100 испытаний каждое, Concatи Containsкаждое испытание включало выбор 25 строк, заданных рандомизированным списком первичных ключей. Я запускал это около дюжины раз, и в большинстве случаев Concatметод работает на 5-10% быстрее, хотя один раз Containsметод выигрывал совсем немного.

DCShannon
источник
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();
Дипан Радж
источник
0

Вот как я это делаю с помощью 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 в основном почти равен O (1), поэтому ваша сложность остается O (n).

MG
источник
Речь идет о LINQ-to-SQL. Такие соображения по LINQ-to-объектам не применяются.
Герт Арнольд
ICollection также может поступать из LINQ-SQL, это общий способ
MG
Вопрос в том, как построить выражение, переводящее в правильный SQL. Это не имеет ничего общего с поиском в местной коллекции. Ваш ответ только введет в заблуждение будущих читателей, которые не осознают этого различия.
Герт Арнольд