Reputation: 669
How to display children and grandchildren when 'parentItemNo' is given.
Below is my model: Any record (itemNo) can become a parent, child, or grandchild.
public partial class item
{
public string ItemNo { get; set; }
public string ParentItemNo { get; set; }
}
Below query returns children: Would like to display grandchildren as well.
var result = Context.Items.Where(p => p.ParentItemNo == parentItemNo).Select(p => p.ItemNo).ToListAsync(); ;
Upvotes: 0
Views: 1133
Reputation: 34988
Navigation properties.
Given an Item class which can have multiple levels where each item can have an optional Parent:
public class Item
{
[Key]
public string ItemNo { get; set; }
[ForeignKey("Parent")]
public string ParentItemNo { get; set; }
public virtual Item Parent { get; set; }
public virtual ICollection<Item> Children { get; set; } = new List<Item>();
}
From here I would recommend setting up the mapping explicitly, either in the DbContext OnModelCreating event or using an EntityTypeConfiguration implementation:
modelBuilder.Entity<Item>()
.HasOptional(x => x.Parent)
.WithMany(x => x.Children);
When you want to get the children and grand-children for an item:
var children = await context.Items
.Include(x => x.Children) // eager load children
.ThenInclude(x => x.Children) // eager load grandchildren
.Where(x => x.ItemNo == parentItemNo)
.SelectMany(x => x.Children)
.ToListAsync();
Normally though you'd load the parent with it's children and grandchildren eager loaded:
var parent = await context.Items
.Include(x => x.Children)
.ThenInclude(x => x.Children)
.SingleAsync(x => x.ItemNo == parentItemNo);
For eager loading you need to plan ahead with what data you expect to need, otherwise you should ensure lazy loading is enabled and available if it's possible that a grandchild could itself have children items. Iterating down to this level would either attempt a lazy load (possibly resulting in errors if the object is orphaned from it's DbContext) or return no data or a partial representation of data if the DbContext was already tracking some of the great grandchildren that it could fill in.
Edit: if you just want to get the Item Numbers of the children and grandchildren then you don't need to use the eager loading with Include
as that would be to return entities and their related entities. Projection with Select
/ SelectMany
does not need this.
To select the ItemNos of any Children and Grandchildren:
var childDetails = await context.Items
.Where(x => x.ItemNo == parentItemNo)
.SelectMany(x => x.Children.Select(c => new
{
c.itemNo,
GrandChildItemNos = c.Children.Select(gc => gc.itemNo).ToList()
}).ToListAsync();
What this will give you is a list of anonymous types containing each child's Item #, and the list of that child's grandchild item #s. From there you can combine all of the item #s:
List<string> itemNumbers = childDetails.Select(x => x.itemNo).ToList();
foreach(var child in childDetails)
itemNumbers.AddRange(child.GrandChildItemNos);
This would combine all of the item numbers for the children and their grandchildren into a single list. From there if relationships to a particular child could be doubled up (2 children share the same grandchild) you can add a Distinct()
to the end to remove duplicates.
It may be possible to select both child and grand child in the EF Linq operation using something like a Union but if possible I'd expect it to be a fair bit more complex and might be difficult to modify and understand later on.
Upvotes: 1