SelectMany Three Levels Deep

Я могу сгладить результаты дочерней коллекции внутри коллекции с помощью SelectMany:

 // a list of Foos, a Foo contains a List of Bars
 var source = new List<Foo>() { ... };

 var q = source.SelectMany(foo => foo.Bar)
     .Select(bar => bar.barId)
 .ToList();

это дает мне список всех идентификаторов баров в списке Foo. Когда я пытаюсь пройти три уровня в глубину, возвращается неверный результат.

 var q = source.SelectMany(foo => foo.Bar)
     .SelectMany(bar => bar.Widget)
         .Select(widget => widget.WidgetId)
 .ToList();

Как мне использовать SelectMany, чтобы получить список всех виджетов во всех барах в моем списке Foos?

Изменить Я неправильно выразился в приведенном выше предложении, но код отражает цель. Я ищу список всех идентификаторов виджетов, а не виджетов.

"Неправильный" результат - возвращаются не все идентификаторы виджетов.


person blu    schedule 12.03.2009    source источник
comment
Выглядит нормально для меня. возвращается неверный результат, это не описательное сообщение об ошибке, что вы получаете и чего ожидаете?   -  person erikkallen    schedule 12.03.2009


Ответы (3)


Ваш запрос возвращает все идентификаторы виджетов, а не все виджеты. Если вам просто нужны виджеты, просто используйте:

var q = source.SelectMany(foo => foo.Bar)
              .SelectMany(bar => bar.Widget)
              .ToList();

Если это по-прежнему дает "неправильный результат", объясните, как это неправильный результат. Пример кода был бы очень полезен :)

РЕДАКТИРОВАТЬ: Хорошо, если вам нужны идентификаторы виджетов, ваш исходный код должен подойти:

var q = source.SelectMany(foo => foo.Bar)
              .SelectMany(bar => bar.Widget)
              .Select(widget => widget.WidgetId)
              .ToList();

Это также может быть записано как

var q = (from foo in source
         from bar in foo.Bar
         from widget in bar.Widget
         select widgetId).ToList();

если вам нравится формат выражения запроса.

Это действительно должно работать. Если это не работает, это говорит о том, что с вашими данными что-то не так.

Мы должны были проверить раньше — это просто LINQ to Objects или более сложный провайдер (например, LINQ to SQL)?

person Jon Skeet    schedule 12.03.2009
comment
Да, я хотел сказать, что возвращены все идентификаторы виджетов, а не виджеты. Когда я связываю SelectMany(...).SelectMany(...).Select(), последний выбор по какой-то причине не возвращает список всех идентификаторов виджетов. - person blu; 13.03.2009
comment
Его LINQ-to-Objects. Хорошо, если запрос точен с точки зрения того, как я ожидаю, что он будет работать, я могу сузить проблему до данных и перейти оттуда, спасибо. - person blu; 13.03.2009

var q = (
    from f in foo
    from b in f.Bars
    from w in b.Widgets
    select w.WidgetId
   ).ToList();

Также обратите внимание, что если вам нужен уникальный список, вы можете вместо этого использовать .Distinct().ToList().

person eglasius    schedule 12.03.2009
comment
Это предполагает наличие одного бара на Foo и одного виджета на бар. - person Jon Skeet; 12.03.2009
comment
@ Джон, но разве это не то, что он делает? - person eglasius; 12.03.2009
comment
Нет. Он выбирает несколько баров для каждого Foo - это то, что делает SelectMany. - person Jon Skeet; 12.03.2009
comment
См. комментарий: Foo содержит список баров. Ваша исправленная версия в основном такая же, как и моя, только занимает в два раза больше строк кода (или в три раза больше, если ToList() на самом деле не требуется). Иногда выражения запросов доставляют больше хлопот, чем пользы :) - person Jon Skeet; 12.03.2009
comment
@ Джон, верно, не привык к моделям, говорящим foo.Bar для 1-n :( -- обновлено только с 1-n версией. - person eglasius; 13.03.2009
comment
@Jon Я часто использую эти методы, хотя мне нравится, как читаются некоторые выражения :) - теперь, когда это с WidgetId, это не было так уж неточно;) - person eglasius; 13.03.2009

       var q = source.SelectMany(foo => foo.Bar)
          .SelectMany(bar => bar.Widget,(bar,widget) => widget.WidgetId)
          .ToList();

мы можем вызвать эту перегрузку SelectMany(), чтобы мы могли указать проекцию, используя лямбда-выражение

person Abdalwhab Bakheet    schedule 29.01.2013
comment
здесь мы в конечном итоге вызываем три метода вместо четырех - person Abdalwhab Bakheet; 29.01.2013