对象引用未设置为尝试使用LINQ递归查找对象的对象错误的实例

时间:2012-12-10 16:54:15

标签: c# linq

我正在尝试更新对象图中可能跨越'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>

3 个答案:

答案 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);
}