Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use LINQ to project this parent and children object model into a flat, single object?

I'm trying to flatten out a simple class that has a parent + child array, into a single class.

From:

public class Foo
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<PewPew> PewPews { get; set; }
}

public class PewPew
{
    public string Name { get; set; }
    public string Whatever { get; set; }
}

- 1 | Fred | { { AAA | xxx }, { BBB | yyy } }
- 2 | Bill | { { CCC | zzz } }

To:

public class FooProjection
{
    public int Id { get; set; }
    public string Name { get; set; }
    public PewPewName { get; set; }
    public PewPewWhatever { get; set; }
}

- 1 | Fred | AAA | xxx
- 1 | Fred | BBB | yyy
- 2 | Bill | CCC | zzz
like image 480
Pure.Krome Avatar asked Sep 08 '11 01:09

Pure.Krome


2 Answers

The pure Linq approach

You can use the SelectMany() overload that allows you to specify a result selector that is called on every element in the collection:

Projects each element of a sequence to an IEnumerable, flattens the resulting sequences into one sequence, and invokes a result selector function on each element therein.

List<Foo> foos = new List<Foo>();
var fooProjections = foos.SelectMany(x => x.PewPews, (foo, pew) => new FooProjection() 
{ 
    Id = foo.Id, 
    Name = foo.Name,
    PewPewName = pew.Name,
    PewPewWhatever = pew.Whatever 
}).ToList();

This approach is the most concise, but takes some time getting used to, especially if you have not been working with Linq a lot.

Edit:

As per @AS-CII's comment it might be more readily understandable (and this is important for maintaining the code base) to just use a loop and a simple projection with Select(). If someone has a problem with Linq in this scenario at all, two nested loop would do as well. I'll show both for completeness.

Using a foreach loop and a Select projection

Just iterate over all Foos and create a new FooProjection for each PewPew in the current item. Add all of them to the fooProjections list that is in scope. This approach uses a Linq projection to map from each PewPew to a FooProjection, using the foo from the foreach loop.

List<Foo> foos = new List<Foo>();
List<FooProjection> fooProjections = new List<FooProjection>();

foreach(var foo in foos)
{
    var someFooProjections = foo.PewPews.Select(x => new FooProjection() 
    { 
        Id = foo.Id, 
        Name = foo.Name, 
        PewPewName = x.Name, 
        PewPewWhatever = x.Whatever 
    });
    fooProjections.AddRange(someFooProjections);
}

Using two nested foreach loops

Just use two foreach loops, the outer iterating over the Foos, the inner over the PewPews collection in the current foo - add a new FooProjection to the fooProjections list that is in scope. This approach does not make use of Linq at all.

List<FooProjection> fooProjections = new List<FooProjection>();
foreach (var foo in foos)
    foreach (var pew in foo.PewPews)
    {
        fooProjections.Add(new FooProjection()
        {
            Id = foo.Id,
            Name = foo.Name,
            PewPewName = pew.Name,
            PewPewWhatever = pew.Whatever
        });
    }
like image 70
BrokenGlass Avatar answered Dec 08 '22 10:12

BrokenGlass


I find query expressions with multiple froms far easier to write than calls to SelectMany. They compile to the same thing.

List<Foo> foos = GetFoos();

var projected = 
    from foo in foos
    from pewPew in foo.PewPews
    select new FooProjection
        { Id = foo.Id, 
          Name = foo.Name, 
          PewPewName = pewPew.Name, 
          PewPewWhatever = pewPew.Whatever };
like image 29
AakashM Avatar answered Dec 08 '22 11:12

AakashM