6

I have a enumerable list that contains a flattened parent-child relationship:

ParentGuid1, ParentName1, ChildGuid1, ChildName1
ParentGuid1, ParentName1, ChildGuid2, ChildName2
ParentGuid2, ParentName2, ChildGuid3, ChildName3
ParentGuid2, ParentName2, ChildGuid4, ChildName4

I have defined a Child class and a Parent class that includes a List<Child> property called Children.

Can I use linq to create on object graph with one instance of the Parent class per unique ParentGuid, referencing a List populated by the children associated with that parent.

Something along the lines of this (note, this code doesn't compile):

myFlattenedHierarchy.Select(p => new Parent
   {Guid = p.ParentGuid, 
    Name = p.ParentName, 
    Children = myFlattenedHierarchy.Where(c => c.ParentGuid == p.ParentGuid).Select(c => new Child{Guid = c.ChildGuid, Name = c.ChildName})
   });
Adam Flynn
  • 949
  • 2
  • 9
  • 21
  • Is this graph only 2 levels deep, i.e. `Parent 1-* Child` without cycles? Or are the GUID's global and each `ChildGuid` could be a `ParentGuid`? – user7116 Sep 29 '11 at 14:08

5 Answers5

6
myFlattenedHierarchy.Select(p => new Parent
   {Guid = p.ParentGuid, 
    Name = p.ParentName, 
    Children = myFlattenedHierarchy.Where(c => c.ParentGuid == p.ParentGuid).Select(c => new Child{Guid = c.ChildGuid, Name = c.ChildName})
   });

You should be able to do that, but the Children can not be a List, it has to be IEnumerable.

AD.Net
  • 13,352
  • 2
  • 28
  • 47
3

Here's the pre-Linq way to do it with a simple loop.

Dictionary<Guid, Parent> parents = new Dictionary<Guid, Parent>();
foreach(RowType row in myFlattenedHierarchy) //just enumerate once
{
  if (!parents.ContainsKey(row.ParentGuid)
  {
    Parent newParent = new Parent(row);
    parents[row.ParentGuid] = newParent;
  }

  Child newChild = new Child(row);

  Parent theParent = parents[row.ParentGuid];
  theParent.Children.Add(newChild);  
}

List<Parent> result = parents.Values.ToList();

Or you could use GroupBy to get a similar result.

from row in myFlattenedHierarchy
group row by row.ParentGuid into g
select new Parent()
{
  Guid = g.Key,
  Name = g.First().ParentName,
  Children =
  (
    from childRow in g
    select new Child()
    {
      Guid = childrow.ChildGuid,
      Name = childrow.ChildName
    }
  ).ToList()
}

It's a toss-up which is more maintainable. Either way, don't re-enumerate myFlattenedHierarchy inside the loop/query.

Amy B
  • 108,202
  • 21
  • 135
  • 185
  • I think your second example needs a bit of tweaking, `ParentGuid` & `ParentName` aren't properties of `row`, or am I missing something? – Dog Ears Aug 28 '13 at 20:48
  • @DogEars yes, row wasn't in scope where I was using it - edited to use g instead. ParentGuid and ParentName are there as this is a flattenedHierarchy (parent and child information in each row). – Amy B Aug 29 '13 at 00:10
  • Can you please help me with this linq query:http://stackoverflow.com/questions/38120664/how-to-group-by-on-2-child-entities-and-get-total-of-both-this-child-entities – I Love Stackoverflow Jun 30 '16 at 13:17
2

I believe you can use GroupBy() (full disclosure: not compiled):

myFlattenedHierarchy.GroupBy(row => row.ParentGuid)
    .Select(group => new Parent
        {
            Guid = group.Key.ParentGuid,
            Name = group.Key.ParentName,
            Children = myFlattenedHierarchy.Where(c => c.ParentGuid == group.Key.ParentGuid)
                .Select(c => new Child{ Guid = c.ChildGuid, Name = c.ChildName })
                .ToList()
        });
Joaquim Rendeiro
  • 1,388
  • 8
  • 13
0

This should work, very similar to David B's second example but I couldn't get his to work without a bit of fixing (grouping by multiple column) so I've added it here for the record.

from row in myFlattenedHierarchy
group row by new { row.ParentGuid, row.ParentName } into g
select new Parent()
{
  Guid = g.Key.ParentGuid,
  Name = g.Key.ParentName,
  Children =
  (
    from childRow in g
    select new Child()
    {
      Guid = childRow.ChildGuid,
      Name = childRow.ChildName
    }
  ).ToList()
};
Dog Ears
  • 9,637
  • 5
  • 37
  • 54
0

You need to tackle with recursivity and infinite recursivity if you have a loop from within your flat collection. Linq can't be used for the complete problem but can help returning child of a specific node.

VdesmedT
  • 9,037
  • 3
  • 34
  • 50