【问题标题】:need an algorithm to create nested categories in c#需要一种算法来在 C# 中创建嵌套类别
【发布时间】:2010-08-12 00:35:56
【问题描述】:

我有一个如下表结构:

categoryID bigint , primary key , not null
categoryName nvarchar(100) 
parentID bigint, not null

categoryID 和 parentID 是一对多的关系

我想在我的程序中创建一个无限深度的嵌套类别。

我有一个解决方案,但效果不太好,只返回根请看代码:

    private static string createlist(string catid, string parent)
    {

        string sql = "SELECT categoryID , categoryName FROM category WHERE parentID = " + parent;
        SqlConnection cn = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=sanjab;Integrated Security=True");
        cn.Open();
        SqlCommand cmd = new SqlCommand(sql, cn);
        SqlDataReader sdr = cmd.ExecuteReader();

        while (sdr.Read())
        {
            if (catid != "")
                catid += ", ";
            catid += sdr[1].ToString();
            createlist(catid, sdr[0].ToString());


        }
        return catid;
    }

虽然代码效率不高,因为它同时打开了很多连接,但是借助上面的代码和一点点调整,我可以管理 2 级深度类别,但除此之外意味着很多麻烦对我来说。

有没有更简单的方法或算法?

问候。

【问题讨论】:

  • 为什么需要这种类型的解决方案?你需要它的场景是什么?
  • 我也会重新标记 sql。我认为您正在寻找一种方法来编写一个查询,该查询在单个查询中返回所有子项,无论深度如何。
  • 我做过这种事情,使用 xsl,但在那种情况下,这是因为我想要 html 作为最终结果。因此,根据您的需求,这可能值得探索。 (您可能想尝试在搜索词中包含“层次结构”)。

标签: c# sql


【解决方案1】:

假设您想要一次处理整个 Category 层次对象图,并且假设您正在处理合理数量的 Categories(故意强调),那么您最好的选择可能是一次性从 SQL 数据库中加载所有数据,然后在内存中构建树对象图,而不是针对树中的每个节点访问数据库,这可能会导致数百次数据库查找。

例如,如果有 5 个类别,每个类别都有 5 个子类别,并且每个子类别有 5 个子子类别,那么您正在查看 31 个数据库命中以使用来自数据库点的递归来加载它 -即使您只处理 125 条实际的数据库记录。一次性选择所有 125 条记录并不会破坏 31+ 数据库命中的可能性。

为此,我首先将您的类别作为扁平列表(伪代码)返回:

public IList<FlattenedCategory> GetFlattenedCategories()
{
    string sql = "SELECT categoryID, categoryName, parentID FROM category";
    SqlConnection cn = // open connection dataReader etc. (snip)

    while (sdr.Read())
    {
        FlattenedCategory cat = new FlattenedCategory();
        // fill in props, add the 'flattenedCategories' collection (snip)
    }

    return flattenedCategories;
}

FlattenedCategory 类看起来像这样:

public class FlattenedCategory
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public int? ParentId { get; set; }
}

现在我们有一个所有类别的内存集合,我们像这样构建树:

public IList<Category> GetCategoryTreeFromFlattenedCollection(
    IList<FlattenedCategory> flattenedCats, int? parentId)
{
    List<Category> cats = new List<Category>();

    var filteredFlatCats = flattenedCats.Where(fc => fc.ParentId == parentId);

    foreach (FlattenedCategory flattenedCat in filteredFlatCats)
    {
        Category cat = new Category();
        cat.CategoryId = flattenedCat.CategoryId;
        cat.Name = flattenedCat.Name;

        Ilist<Category> childCats = GetCategoryTreeFromFlattenedCollection(
            flattenedCats, flattenedCat.CategoryId);

        cat.Children.AddRange(childCats);

        foreach (Category childCat in childCats)
        {
            childCat.Parent = cat;
        }

        cats.Add(cat);
    }

    return cats;
}

然后这样称呼它:

IList<FlattenedCategory> flattenedCats = GetFlattenedCategories();
Ilist<Category> categoryTree = GetCategoryTreeFromFlattenedCollection(flattenedCats, null);

注意:在此示例中,我们使用 Nullable INT 作为 ParentCategoryId,可为 null 的值意味着它是根(顶级)类别(无父级)。我建议您也将数据库中的 parentID 字段设为空。

警告:代码未经测试,只是伪代码,因此使用风险自负。这只是为了展示总体思路。

【讨论】:

    【解决方案2】:

    我将首先创建一个 Category 类来保存您的类别和子类别。这将使您能够实现递归读出无限深的子类别。它看起来像这样:(免责声明以下内容均未针对编译器进行测试,因此可能存在一些语法错误)

    public class Category{
         public int CategoryId { get; private set; }
         public string CategoryName { get; private set; }
         public int ParentId { get; private set; }
         public List<Category> ChildCategories { get; private set; }
    
         //I would recommend using a parentId of 0 or some other int value that can't occur in your database to describe a category that has no parent.
         public Category(int categoryId, string categoryName, int parentId)
         {
             CategoryId = categoryId;
             CategoryName = categoryName;
             ParentId = parentId;
             ChildCategories = getChildCategories();
         }
    
         //I didn't have this method setting the ChildCategories directly because I would 
         //recommend refactoring your data access code into a data access class of some sort
         //also depending on the amount of categories you could be generating a lot of database calls.
         //It may make more sense to get the whole table and pass in the resulting dataset, then use recursion to build your nested category structure.
         private List<Category> getChildCategories()
         {
              List<Category> resultingCategories = new List<Category>();
              //Data access logic here to get a dataset assume the variable holding the  
              //data reader is named sdr
    
              while (sdr.Read()){
                  //This assumes categoryId is the first column
                  int childCategoryId = Convert.ToInt32(sdr[0]);
    
                  //This assumes categoryName is the second column
                  string childCategoryName = sdr[1];
    
                  Category childCategory = new Category(childCategoryId, childCategoryName, CategoryId);
                  resultingCategories.Add(childCategory);
              }
              return resultingCategories;
         }
    }
    

    即使您控制传递到数据访问函数的所有数据,也只是一个附带项目,请参数化您的查询。它将确保您的应用不会陷入如此常见的 SQL 注入攻击。

    【讨论】:

      【解决方案3】:

      正如我在cmets中所说,我认为这更像是一个sql问题。

      我之前的示例有 2 个表。我的错,这看起来更好: SQL Parent/Child recursive call or union?

      这里有一个很好的谷歌搜索来找到更多: http://www.google.com/search?q=sql+children+recursive+site%3Astackoverflow.com

      通用表表达式正是为此目的而制作的: http://msdn.microsoft.com/en-us/library/ms190766.aspx

      接受的答案很好,并且在 C# 中强制执行此操作是可以接受的。但是,只有当您的类别是一个小列表时才好,并且将继续是一个小列表。这正是在 3 年内随着表的增长而遭受性能问题的事情。

      【讨论】:

        【解决方案4】:

        嵌套集是该等式的 SQL 端的一种广受好评的解决方案。它需要更多的应用程序逻辑,因此您需要尽可能抽象出行为。

        如果元素经常插入或删除,它们会有一些限制,但这不应该适用于您的情况。当您确实频繁插入或删除时,解决方案通常是在子级之间创建编号间隔。

        http://www.sqlteam.com/article/more-trees-hierarchies-in-sql 引用了有关该主题的早期 Celko 文章,以及该主题的一些当代变体。

        【讨论】:

        • 完成这项工作的非常有效的方式就是您所展示的方式。多谢。我希望我可以将多个答案标记为正确的答案。
        • 谢谢。我还应该澄清一下,我直接引用的文章提出了嵌套集(邻接列表)的替代方案。最后,如果您使用的是 Sql Server 2008,还有一个附加选项,因为有一个 Hierarchy ID 数据类型。它有其自身的局限性,但请记住:msdn.microsoft.com/en-us/magazine/cc794278.aspx
        【解决方案5】:

        您应该在代码中进行递归,以便使用“select * from yourtable”将所有必要的数据存储到您的内存中,然后执行递归方法。这会快很多。

        【讨论】:

        • 如果要将所有记录放入内存并在那里查询,为什么还要有数据库?如果有数百万条记录怎么办?
        • @itchi - 如果它是电子商务网站的类别树,那么不太可能有数百万条记录。这取决于使用情况,但是一次从数据库中加载所有类别会更有效,因为从数据库 POV 完成的递归会在数据库上产生很多命中,尽管我想你可以使用游标或其他东西从存储过程中做到这一点.
        • @Sunday - 不需要光标/StoredProc msdn.microsoft.com/en-us/library/ms190766.aspx
        猜你喜欢
        • 2022-10-30
        • 2020-04-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-13
        • 2010-12-04
        相关资源
        最近更新 更多