views:

2353

answers:

2
+2  Q: 

C# SelectMany

I can flatten the results of a child collection within a collection with 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();

this gives me the list of all Bar Ids in the Foo List. When I attempt to go three levels deep the incorrect result is returned.

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

How should I be using SelectMany to get the list of all Widgets in all Bars in my list of Foos?

Edit I miss-worded the above sentence, but the code reflects the goal. I am looking for a list of all Widget Ids, not widgets.

An "incorrect" result is not all of the widget ids are returned.

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

Also note that if you want the unique list, you can do .Distinct().ToList() instead.

eglasius
That's assuming a single Bar per Foo, and a single Widget per Bar.
Jon Skeet
@Jon but isn't that precisely what he is doing?
eglasius
No. He's selecting multiple Bars per Foo - that's what SelectMany does.
Jon Skeet
See comment: "a Foo contains a List of Bars". Your amended version is basically the same as mine, just taking twice as many lines of code (or three times as many if ToList() isn't really required). Sometimes query expressions are more hassle than they're worth :)
Jon Skeet
@Jon that's right, not used to models saying foo.Bar for the 1-n :( --updated with just the 1-n version.
eglasius
@Jon I do use the methods a lot, although I like how some of the expressions read :) - now that it is with WidgetId it wasn't that off in lines ;)
eglasius
+9  A: 

Your query is returning all the widget IDs, instead of all the widgets. If you just want widgets, just use:

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

If that's still giving "the incorrect result" please explain in what way it's the incorrect result. Sample code would be very helpful :)

EDIT: Okay, if you want the widget IDs, your original code should be fine:

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

That could also be written as

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

if you like query expression format.

This really should work - if it's not working, that suggests there's something wrong with your data.

We should have checked before - is this just LINQ to Objects, or a fancier provider (e.g. LINQ to SQL)?

Jon Skeet
Yeah, I meant to say all Widget Ids, not Widgets returned. When I chain SelectMany(...).SelectMany(...).Select() the last select doesn't return the list of all Widget Ids for some reason.
blu
Its LINQ-to-Objects. Ok as long as the query is accurate in terms of how I expect it to work I can narrow the issue down to the data and go from there, thank you.
blu