Выберите несколько записей на основе списка идентификаторов с помощью linq

122

У меня есть список, содержащий идентификаторы моей UserProfileтаблицы. Как я могу выбрать все UserProfilesна основе списка идентификаторов, которые я varиспользую LINQ?

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(......);

Я тут застрял. Я могу сделать это с помощью циклов for и т. Д. Но я бы предпочел сделать это с помощью LINQ.

Yustme
источник
4
поиск и поиск - это две разные вещи. Но поскольку вы можете заглянуть через мое плечо в Интернет, не могли бы вы сказать мне, откуда вы знаете, что я не искал? подожди, не говори! Вы все правильно поняли? моя точка зрения точно.
Юстме
5
Задание вопроса требует больше времени, чем поиск. в следующий раз просто предположите, что «он / она» провел обыск или 10.
Юстме
2
Этому по-прежнему уделяется довольно много внимания, поэтому я подумал, что упомянул бы, что ReSharper очень хорошо предлагает места, где вы можете превратить итеративный код в операторы LINQ. Для людей, плохо знакомых с LINQ, он может быть незаменимым инструментом только для этой цели.
Yuck

Ответы:

207

Вы можете использовать Contains()для этого. Когда вы действительно пытаетесь создать INпредложение, это будет немного назад , но это должно сработать:

var userProfiles = _dataContext.UserProfile
                               .Where(t => idList.Contains(t.Id));

Я также предполагаю, что каждая UserProfileзапись будет иметь int Idполе. Если это не так, вам придется внести соответствующие изменения.

дрянь
источник
Привет, да, записи профиля пользователя содержат идентификаторы. Так почему-то я бы делал что-то вроде t => t.id == idList.Contains (id)?
Юстме
Contains()будет обрабатывать эту проверку равенства для каждого idзначения, если вы используете его, как я написал в ответе. Вам не нужно явно ничего писать, ==когда вы пытаетесь сравнить элементы одного набора (массива) с другим (таблица базы данных).
Фу
Проблема в том, что t содержит весь объект UserProfile, а idList содержит только int. Компилятор на что-то пожаловался, но мне удалось это исправить. Спасибо.
Юстме
2
@Yuck - У меня не работает, говорит, что функция истекла! Отключили отложенную загрузку, но все равно не удается.
bhuvin
1
Я получаю «Невозможно преобразовать лямбда-выражение в тип int, потому что это не тип делегата». Как это исправить?
Stian
92

Решение с .Where и .Contains имеет сложность O (N квадрат). Простой .Join должен иметь намного лучшую производительность (близкую к O (N) из-за хеширования). Итак, правильный код:

_dataContext.UserProfile.Join(idList, up => up.ID, id => id, (up, id) => up);

А теперь результат моего измерения. Я создал 100 000 UserProfiles и 100 000 id. Присоединение заняло 32 мс, а. Где с .Contains - 2 минуты 19 секунд! Я использовал чистый IEnumerable для этого тестирования, чтобы доказать свое утверждение. Если вы используете List вместо IEnumerable, .Where и .Contains будут быстрее. В любом случае разница существенная. Самый быстрый .Where .Contains - с Set <>. Все зависит от сложности базовых сочетаний для .Contains. Посмотрите этот пост, чтобы узнать о сложности linq. Взгляните на мой тестовый образец ниже:

    private static void Main(string[] args)
    {
        var userProfiles = GenerateUserProfiles();
        var idList = GenerateIds();
        var stopWatch = new Stopwatch();
        stopWatch.Start();
        userProfiles.Join(idList, up => up.ID, id => id, (up, id) => up).ToArray();
        Console.WriteLine("Elapsed .Join time: {0}", stopWatch.Elapsed);
        stopWatch.Restart();
        userProfiles.Where(up => idList.Contains(up.ID)).ToArray();
        Console.WriteLine("Elapsed .Where .Contains time: {0}", stopWatch.Elapsed);
        Console.ReadLine();
    }

    private static IEnumerable<int> GenerateIds()
    {
       // var result = new List<int>();
        for (int i = 100000; i > 0; i--)
        {
            yield return i;
        }
    }

    private static IEnumerable<UserProfile> GenerateUserProfiles()
    {
        for (int i = 0; i < 100000; i++)
        {
            yield return new UserProfile {ID = i};
        }
    }

Вывод в консоль:

Истекло. Время присоединения: 00: 00: 00.0322546

Прошло .Где .Содержит время: 00: 02: 19.4072107

Дэвид Грегор
источник
4
Вы можете подтвердить это цифрами?
Юстме 03
Приятно, однако мне интересно, каковы будут тайминги при Listиспользовании. +1
Юстме 04
Хорошо, вот интересующие вас тайминги: List занял 13,1 секунды, а HashSet - 0,7 мс! Таким образом, .Where .Contains лучше всего только в случае HashSet (когда .Contains имеет сложность O (1)). В других случаях .Join лучше
Дэвид Грегор
5
Я получаю сообщение Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.об ошибке при использовании контекста данных LINQ2SQL.
Mayank Raichura
3
@Yustme - производительность всегда важна . (Я ненавижу быть парнем «это должен быть принятый ответ», но ...)
jleach
19

Хорошие ответы выше, но не забывайте одну ВАЖНУЮ вещь - они дают разные результаты!

  var idList = new int[1, 2, 2, 2, 2]; // same user is selected 4 times
  var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e)).ToList();

Это вернет 2 строки из БД (и это может быть правильно, если вам просто нужен отдельный отсортированный список пользователей)

НО во многих случаях вам может понадобиться несортированный список результатов. Вы всегда должны думать об этом как о SQL-запросе. Пожалуйста, просмотрите пример с корзиной покупок в интернет-магазине, чтобы проиллюстрировать, что происходит:

  var priceListIDs = new int[1, 2, 2, 2, 2]; // user has bought 4 times item ID 2
  var shoppingCart = _dataContext.ShoppingCart
                     .Join(priceListIDs, sc => sc.PriceListID, pli => pli, (sc, pli) => sc)
                     .ToList();

Это вернет 5 результатов из БД. В этом случае использование «содержит» было бы неправильным.

Tomino
источник
13

Это должно быть просто. Попробуй это:

var idList = new int[1, 2, 3, 4, 5];
var userProfiles = _dataContext.UserProfile.Where(e => idList.Contains(e));
Фабиан Биглер
источник