Yahya Hussein
Yahya Hussein

Reputation: 9101

c# - query nested type with LINQ

I have a model that looks like the following:

public class MyType{
 public string Id {get;set;}
 public string Name{get;set;}
 public List<MyType> Children{get;set;}
}

and in my data I have just two level data, meaning my objects will look like:

{
 MyType{"1","firstParent",
 {
  MyType{"2","firstChild",null},
  MyType{"3","secondChild",null}}
 },

 MyType{"4","secondParent",
 {
  MyType{"5","firstChild",null},
  MyType{"6","secondChild",null}}
 }
}

How do I query to get MyType object with a specific Id where it might be a parent or child?

The following will return only parents.

collection.FirstOrDefault(c => c.id==id)

Upvotes: 4

Views: 1122

Answers (4)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249476

You can use Any with a recursive local function to find objects on any level (your data structure would seem to indicate a deeper level is possible)

bool hasIdOrChildren(MyType t, string localId)
{
    return t.Id == localId || (t.Children != null && t.Children.Any(o => hasIdOrChildren(o, localId)));
}
collection.FirstOrDefault(c => hasIdOrChildren(c, id));

Or using pre C#7 syntax:

Func<MyType, string, bool> hasIdOrChildren = null;
hasIdOrChildren = (MyType t, string localId) =>
{
    return t.Id == localId || (t.Children != null && t.Children.Any(o => hasIdOrChildren(o, localId)));
};
collection.FirstOrDefault(c => hasIdOrChildren(c, id));

If you are only interested in one level, you can drop the reclusiveness:

collection.FirstOrDefault(c => c.Id == id || (c.Children != null && c.Children.Any(o => o.Id == id)));

Edit

The code above gives the parent if any child has the id, you can also flatten the whole tree structure using SelectMany also with a recursive function:

IEnumerable<MyType> flattenTree(MyType t)
{
    if(t.Children == null)
    {
        return new[] { t };
    }
    return new[] { t }
        .Concat(t.Children.SelectMany(flattenTree));
};
collection
    .SelectMany(flattenTree)
    .FirstOrDefault(c => c.Id == id);

This method can be useful for any type of processing where you need to flatten the tree.

Upvotes: 7

Alexander
Alexander

Reputation: 4527

I think, you should flatten collection using SelectMany method, then use FirstOrDefault to get element by id:

MyType selected = collection
    .SelectMany(obj => new MyType[] {obj, obj.NestedList})
    .FirstOrDefault(obj => obj.id == id);

Upvotes: 1

Chrᴉz remembers Monica
Chrᴉz remembers Monica

Reputation: 1904

I think you're looking for

var flattenedList = IEnumerable.SelectMany(i => i.ItemsInList);

This flattens the list and gives back one list with all items in it. In your case you need to select

collection.SelectMany(c => c.Type).Concat(collection).Where(item => item.Id == 5);

MSDN

You still got the childs in your joined parents here, but you can still erase them or ignore them.

Upvotes: 1

Onyx Caldin
Onyx Caldin

Reputation: 299

You could build a list of all MyType including children and then query on it like this :

collection.SelectMany(c => c.Children).Concat(collection).Where(c => c.id == id)

Upvotes: 3

Related Questions