在实体中使用string.split来遍历树深度

时间:2017-02-11 20:14:31

标签: c# sql entity-framework linq tree

我有以下自引用表

public partial class products_category
    {
    public long id { get; set; }
    public string category_name { get; set; }
    public string category_description { get; set; }
    //self referencing to table id
    public Nullable<long> Parent_Id { get; set; }
    public string navPath {get; set; }
}

此处字符串navpath包含子类别的所有主要父项,例如:

"Clothes" = 1 Parent_id=null, navpath=""
"Silk" = 2 Parent_id=1 navpath="1"
"Silk Suit"=3 parent_id=2 navpath="1-2"
"Saree" =4 parent_id=3 navpath="1-2-3"
"Dress Material"=5 parent_id=1 navpath="1" and so on....

现在,根据这种情况,我想访问展平树以进行某一深度的进一步处理,只能说到2级或直到与navpath相关联的子级别4级。

我对这个问题的看法是以这种方式使用linq:

var catTrees = db.products_category.Where(pc => pc.navpath.Split('-').Length < 4).ToList();

我使用以下链接进行进一步的遍历和树生成: https://bitlush.com/blog/recursive-hierarchical-joins-in-c-sharp-and-linq

到目前为止,它正在做一项伟大的工作,唯一的问题是我不想预先选择整个表进行处理。我希望在第一次迭代时实现分页和一定程度的深度,因此我可以在数千条记录的情况下保持性能。 [将此视为类别层次结构或博客/ youtube评论层次结构]。

但是使用上面的ef linq命令会出现以下错误:

The LINQ expression node type 'ArrayLength' is not supported in LINQ to Entities.

我在ef文档和SO中的其他地方检查过,知道string.split无法隐式使用EF。但是我们可以使用扩展方法应用它吗?或者这个树选择是否可以使用替代方法而不使用string.split并仅使用DB? 请建议。

2 个答案:

答案 0 :(得分:1)

这看起来像是在LINQ mpre中构建SQL代码的问题,特别是SQL,它将字符串拆分为破折号并对元素进行计数。

如果你不讨厌加载到内存然后你可以强迫任何东西:)

var catTrees = db.products_category.ToList().Where(pc => pc.navpath.Split('-').Length < 4).ToList();

这里的技巧是当我们想要数据库中的数据时,通过添加.ToList()来强制执行SQL。这称为实现数据。

即使有了这种实现技巧,计数也会更快

var catTrees = db.products_category.ToList().Where(pc => pc.navpath.Count(a => a == '-') < 3).ToList();

这些解决方案基本上与

相同
List<Result> filter() {
    List<Result> r = new List<Result>();
    foreach(var a in db.products_category) {
        if(a.navpath.Count(a => a == '-') < 3) {
            r.add(a);
        }
    }
    return r;
}

在考虑它时,过滤器方法的内存密集程度要低一些,因为它只读取一个并且永远不会将所有内容存储在内存中。 (理论上至少,只有少数人真正知道.NET编译器在阴影中做了什么)

答案 1 :(得分:0)

我建议你不要使用navpath来检查深度。

如果您可以更改模型,则可以为每个类别添加一个额外的数字Depth字段,并根据其navpath填充它,然后您可以通过以下方式从代码中选择它们:

var catTrees = db.products_category.Where(pc => pc.Depth < 3).ToList();

有很多方法可以填充新列,但最重要的是你必须只执行一次(假设你每次修改类别的navpath时都会跟踪它)。

填充它的一种可能方法是循环遍历所有类别,例如:

var allCategories =  db.products_category.ToList();
foreach(var category in allCategories)
{
  var depth = category.navpath == "" ? 0 : category.navpath.Split('-').Length + 1;
  category.Depth = depth;
}
allCategories.SubmitChanges();