C # фильтровать словарь списка класса на основе свойства класса

#c# #list #linq #dictionary

#c# #Список #linq #словарь

Вопрос:

У меня есть этот словарь Dictionary<string, List<Payments>> , в котором содержатся сотрудники, у каждого сотрудника есть список платежей. Класс Payments имеет строковое свойство с именем PayCategoryId . Я хочу отфильтровать этот словарь и получить только сотрудников с платежами, имеющими определенные значения PayCategoryId, и для каждого сотрудника только эти платежи. Я почти уверен, что этого можно достичь с помощью LINQ, но у меня почти нулевой опыт работы с LINQ, поэтому нужна ваша помощь.

Исходный (нефильтрованный) словарь содержит 76 элементов (сотрудников). Сотрудник, который я буду использовать в качестве примера, имеет 27 платежей, некоторые из которых имеют требуемый идентификатор PayCategoryId.

Что я сделал:

  1. Список с требуемым идентификатором PayCategoryId:
     var payCategoriesID = new List<string> (){ "a", "b", "c" };
  
  1. Частично фильтровать словарь с помощью этого LINQ (я уверен, что это беспорядок, но работает!):
     var result = dict.Where(o => o.Value.Where(x => payCategoriesID.Contains(x.PayCategoryId)).Any()).ToDictionary(mc => mc.Key, mc => mc.Value);
  
  1. Полуфильтрованный результирующий словарь содержит только 34 элемента. Сотрудники, не имеющие платежей с требуемым идентификатором PayCategoryId, были отфильтрованы. Но в моем примере у сотрудника все еще есть все 27 платежей в списке. Мне нужно, чтобы его список тоже был отфильтрован и имел только платежи, имеющие PayCategoryId = один из идентификаторов из списка payCategoriesID.
    Конечно, пример employee — это всего лишь пример, все сотрудники должны быть отфильтрованы.

Не могли бы вы помочь, пожалуйста?

Адриан

Ответ №1:

Вы можете использовать Select() для проецирования в перечислимый анонимный тип и получать только нужное вам значение, а затем создавать словарь.

 var match = dict
        .Select(kv => new
        {
            Employee = kv.Key,
            Payments = kv.Value.Where(p => payCategoriesID.Contains(p.PayCategoryId))
        })
        .Where(emp => emp.Payments.Any())
        .ToDictionary(k => k.Employee, v => v.Payments);
  

На первом шаге создается объект с ключом и отфильтрованными значениями, затем Where() удаляются пустые списки. Таким образом, вы выполняете итерацию по списку идентификаторов только один раз (для каждого элемента в словаре).


Кроме того, на самом деле это не связано, но Any() метод имеет перегрузку, которая принимает предикат, поэтому вместо

 o.Value.Where(x => payCategoriesID.Contains(x.PayCategoryId)).Any()
  

вы можете сделать напрямую

 o.Value.Any(x => payCategoriesID.Contains(x.PayCategoryId))
  

То же самое относится и к другим методам LINQ, таким как Count() , First() , FirstOrDefault() , Last() и другим.

Комментарии:

1. Элегантный, лаконичный и объясненный. Спасибо, @ColinD!

Ответ №2:

 var result = dict.Where(o => o.Value.Where(x => payCategoriesID.Contains(x.PayCategoryId)).Any()).ToDictionary(mc => mc.Key, mc => mc.Value.Where(t=> payCategoriesID.Contains(t.PayCategoryId)));
  

Комментарии:

1. Бинго! И ДОХ! 🙂 Большое спасибо!