tags:

views:

615

answers:

2

Having the following classes (highly simplified):

public class Child
{
    public string Label;
    public int CategoryNumber;
    public int StorageId;
}

public class Parent
{
    public string Label;
    public List<Child> Children = new List<Child>();
}

And having the following data:

var parents = new List<Parent>();

var parent = new Parent() {Label="P1"};
parent.Children.Add(new Child() {Label="C1", CategoryNumber=1, StorageId=10});
parent.Children.Add(new Child() {Label="C2", CategoryNumber=2, StorageId=20});
parents.Add(parent);

parent = new Parent() {Label="P2"};
parent.Children.Add(new Child() {Label="C3", CategoryNumber=1, StorageId=10});
parent.Children.Add(new Child() {Label="C4", CategoryNumber=2, StorageId=30});
parents.Add(parent);

parent = new Parent() {Label="P3"};
parent.Children.Add(new Child() {Label="C5", CategoryNumber=3, StorageId=10});
parent.Children.Add(new Child() {Label="C6", CategoryNumber=2, StorageId=40});
parents.Add(parent);

Now, how would I get a list of children (with CategoryNumber=2) from the list of parents containing at least one child with CategoryNumber = 1 ?

I can do the following but it does not appear to be optimal:

var validParents = from p in parents
                   where p.Children.Any (c => c.CategoryNumber==1)
                   select p;
var selectedChildren = validParents.Select(p => from c in p.Children 
                                                where c.CategoryNumber == 2
                                                select c);

Here's what I get for selectedChildren:

  • IEnumerable<IEnumerable<Child>>
    • IEnumerable<Child>
      • C2 2 20
    • IEnumerable<Child>
      • C4 2 30

Is it possible to only have one flat list containing the two children elements instead of two sub-list? How would it translate in LINQ ?

+2  A: 

You can string a couple queries together, using SelectMany and Where.

var selectedChildren = (from p in parents
                       where p.Children.Any (c => c.CategoryNumber==1)
                       select p)
                       .SelectMany(p => p.Children)
                       .Where(c => c.CategoryNumber == 2);

// or...

var = selectedChildren = parents
                         .Where(p => p.Children.Any(c => c.CategoryNumber == 1))
                         .SelectMany(p => p.Children)
                         .Where(c => c.CategoryNumber == 2);
Scott Ivey
Works great! SelectMany seems to be a useful method. Do you have any suggestion for a web site or a book about fully understanding Linq?
Stecy
Scott Ivey
+2  A: 

Scott's answer is great; I'd just like to point out that you can in fact do this query using query continuation syntax:

from parent in parents 
where parent.Children.Any (c => c.CategoryNumber==1)
select parent into p
from child in p.Children
where child.CategoryNumber == 2
select child

Notice how the "into" lets you pipe the result of one query into the next query. Pretty slick, eh?

Eric Lippert
@Eric: Yeah, the LINQ query syntax for this is quite nice (and probably slightly better to use as opposed to mixing query and extension method syntax). Whenever I encounter this topic, I do however feel that LINQ is missing a concatenation operator (akin to Seq.concat in F#), which could be used as an alternative to SelectMany, and is indeed very handy in other cases. A simple overload of the Enumerable.Concat method that takes a IEnumerable<IEnuerable<T>> parameter would do the job quite well. Any thoughts (or even better, plans) regarding this?
Noldorin
Ah, it seems MoreLINQ does define this operator: http://code.google.com/p/morelinq/wiki/OperatorsOverview. It would be quite nice to have in the BCL though (along with a few others such as Zip).
Noldorin