问题
I have this Project class:
public class Project
{
public int Id { get; set; }
public int? ParentId { get; set; }
public List<Project> ChildProjects { get; set; }
// more properties
}
This is my attempt to load all descendants of any given project:
private async Task<List<Project>> LoadDescendantsOf(Project project)
{
project.ChildProjects = await db.Projects
.Where(p => p.ParentId == project.Id)
.ToListAsync();
foreach (Project childProject in project.ChildProjects)
{
yield return childProject.ChildProjects =
await LoadDescendantsOf(childProject);
}
}
... but it's not working. The error message I get is
The body of 'ProjectsController.LoadDescendantsOf(Project)' cannot be an iterator block because 'Task>' is not an iterator interface type
I have tried making the method synchronous, but it's the same error message, only without the "Task"-part.
How can I make it work?
回答1:
You can write simple extension for this:
public static IEnumerable<T> Traverse<T>(this T e, Func<T, IEnumerable<T>> childrenProvider)
{
return TraverseMany(new[] { e }, childrenProvider);
}
public static IEnumerable<T> TraverseMany<T>(this IEnumerable<T> collection, Func<T, IEnumerable<T>> childrenProvider)
{
var stack = new Stack<T>();
foreach(var c in collection)
{
stack.Push(c);
}
while (stack.Count > 0)
{
var i = stack.Pop();
yield return i;
var children = childrenProvider(i);
if (children != null)
{
foreach (var c in children)
{
stack.Push(c);
}
}
}
}
And usage:
var allProjectIds = p.Traverse(x => x.ChildProjects).Select(x => x.Id).ToList();
If you want to load subprojects I would recommend write recursive SQL procedure on cursors for this, but this will also do nice on small data:
var allProjectIds = p
.Traverse(x =>
{
x.ChildProjects = db.Projects
.Where(p => p.ParentId == project.Id)
.ToList();
return x.ChildProjects;
})
.Select(x => x.Id)
.ToList();
来源:https://stackoverflow.com/questions/58367399/how-can-i-load-all-child-records-recursively