我正在尝试更新对象图中可能跨越'n'级别的项目。
以下是我的对象模型:
public class Entity
{
public string Name { get; set; }
}
public class Category : Entity
{
public List<Category> Categories { get; set; }
public List<Product> Products { get; set; }
}
public class Product : Entity
{
}
我的观点与ObservableCollection<Category> Categories
绑定。我想要做的是给出一个类别名称,我需要检索第一个与该集合匹配的对象。
对于ex,给定一个像这样的列表和一个Facial Tissue类别,我需要从集合中检索Facial Tissue类别对象。
Category - Pharmacy
|-Product - Aspirin
|-Product - Tylenol
|-Category - Tooth Paste
| |-Product - Crest
| |-Product - Colgate
|-Category - Paper Products
|-Category - Toilet Paper
| |-Product - NoName
| |-Product - Charmin
|-Category - Facial Tissue
|-Product - Kleenex
Category - Household
|-Product - Pinesol Cleaner
|-Product - Garbage Bags
我试过这个,但是当我在层次结构中搜索级别&gt; 2时,它会抛出 对象引用未设置为对象的实例 异常。
return Categories.FirstOrDefault(n => n.Name == name) ??
Categories.SelectMany(node => node.Categories).Where(lx => lx.Name == name).FirstOrDefault();
注意:有时类别可能在层次结构的内部为null。如果没有类别,则集合设置为null。此外,解决方案必然不需要使用LINQ。< / p>
答案 0 :(得分:4)
您可以使用以下任一方法递归遍历树结构:
public static IEnumerable<T> Traverse<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> childSelector)
{
var queue = new Queue<T>(source);
while (queue.Any())
{
var item = queue.Dequeue();
yield return item;
foreach (var child in childSelector(item))
{
queue.Enqueue(child);
}
}
}
public static IEnumerable<T> Traverse<T>(T root, Func<T, IEnumerable<T>> childSelector)
{
return Traverse(new[] { root }, childSelector);
}
单个根项目存在重载,而另一个根项目存在重载。
如果您愿意,可以使用实际递归来实现它们,但我更喜欢显式数据结构。如果您希望首先搜索深度而不是第一次搜索,只需将Queue
更改为Stack
并相应地更新方法。
要使用它,你可以这样做:
Category root = new Category();
var searchResult = Traverse(root, item => item.Categories)
.Where(category => category.Name == "testValue")
.FirstOrDefault();
由于Categories
为空,您似乎也会收到空错误。如果可能的话,我会高度鼓励你解决这个问题,而不是处理它。如果实体没有类别,则它应该具有空列表,而不是空列表。话虽如此,如果您有任何空项,可以按如下方式调整Traverse
次调用:
Traverse(root, item => item.Categories ?? Enumerable.Empty<Category>())
答案 1 :(得分:1)
LINQ本身没有depth-first search的专用运算符(在这种情况下你需要它)。但是,根据您的要求,使用简单的递归函数有一个相当简单的解决方案:
// Returns the first category with the given name or null, if none is found
Category findCategory(Category start, String name) {
if (start.name == name) {
return start;
}
if (start.Categories == null) {
return null;
}
return (from c in start.Categories
let found = findCategory(c, name)
where found != null
select found).FirstOrDefault()
}
您可以考虑将没有子类别的类别的Categories
属性设置为空列表而不是null
。这允许你在这里跳过空检查(也可能在很多其他地方)。
答案 2 :(得分:0)
这是一个简单的解决方案,虽然它不仅使用Linq:
public Category GetCategory(string name, List<Category> Categories)
{
Category found = Categories.FirstOrDefault(cat => cat.Name == name);
return found ?? Categories.Select(cat => GetCategory(name,cat.Categories))
.FirstOrDefault(cat => cat != null);
}