【问题标题】:Return unordered list from hierarchical sql data从分层 sql 数据返回无序列表
【发布时间】:2023-04-08 17:33:01
【问题描述】:

我有带有 pageId、parentPageId、title 列的表。

有没有办法使用 asp.net、cte、存储过程、UDF... 任何东西返回无序嵌套列表?

表格如下:

PageID    ParentId    Title
1         null        Home
2         null        Products
3         null        Services
4         2           Category 1
5         2           Category 2
6         5           Subcategory 1
7         5           SubCategory 2
8         6           Third Level Category 1
...  

结果应如下所示:

Home
Products
    Category 1
        SubCategory 1
            Third Level Category 1
        SubCategory 2
    Category 2
Services

理想情况下,列表也应该包含<a> 标签,但如果我找到创建<ul> 列表的方法,我希望我可以自己添加。

编辑 1: 我认为已经有解决方案,但似乎没有。我想保持它尽可能简单并不惜一切代价使用 ASP.NET 菜单进行转义,因为它默认使用表。然后我必须使用 CSS Adapters 等。

即使我决定走“ASP.NET 菜单”路线,我也只能找到这种方法:http://aspalliance.com/822 它使用 DataAdapter 和 DataSet :(

还有更现代或更高效的方法吗?

【问题讨论】:

  • 让我们看看你的 TSQL 尝试,否则人们可能会认为这是一个 plzsendzmethecodez 问题...
  • 我没有进步太多。我刚刚得到了和 Brian 提供的相同的结果,我不知道该怎么办。

标签: asp.net html sql-server-2005


【解决方案1】:

使用 linq2sql 你可以做到:

List<PageInfo> GetHierarchicalPages()
{
   var pages = myContext.PageInfos.ToList();
   var parentPages = pages.Where(p=>p.ParentId == null).ToList();
   foreach(var page in parentPages)
   {
      BuildTree(
        page, 
        p=> p.Pages = pages.Where(child=>p.pageId == child.ParentId).ToList()
        );
   }
}
void BuildTree<T>(T parent, Func<T,List<T>> setAndGetChildrenFunc)
{
   foreach(var child in setAndGetChildrenFunc(parent))
   {
       BuildTree(child, setAndGetChildrenFunc);
   }
}

假设您在 PageInfo 中定义了一个 Pages 属性,例如:

public partial class PageInfo{
   public List<PageInfo> Pages{get;set;}
}

在层次结构中获取它的处理发生在 Web 应用程序端,这避免了 sql 服务器上的额外负载。另请注意,此类信息非常适合缓存。

您可以按照 Rex 所述进行渲染。或者,您可以扩展此实现并使其支持层次结构接口并使用 asp.net 控件。

更新 1:对于您在评论中询问的渲染变化,您可以:

var sb = new System.IO.StringWriter();
var writer = new HtmlTextWriter(sb);
// rex's rendering code
var html = sb.ToString();

【讨论】:

    【解决方案2】:

    最佳做法是使用IHierarchyDataIHierarchalEnumerable 执行此操作,并将DataBind 绑定到从HierarchalDataBoundControl 继承的自定义控件(这是TreeView 等控件的基础)。

    但是,让我们尝试在 c# 中创建一个快速而肮脏、效率不高、简单的示例:

    //class to hold our object graph in memory
    //this is only a good idea if you have a small number of items
    //(less than a few thousand)
    //if so, this is a very flexible and reusable way to represent your tree
    public class Page
    {
        public string Title {get;set;}
        public int ID {get;set;}
        public Collection<Page> Pages = new Collection<Page>();
    
        public Page FindPage(int id)
        {
            return FindPage(this, id);
        }
    
        private Page FindPage(Page page, int id)
        {
            if(page.ID == id)
            {
                return page;
            }
            Page returnPage = null;
            foreach(Page child in page.Pages)
            {
                returnPage = child.FindPage(id);
                if(returnPage != null)
                {
                    break;
                }
            }
            return returnPage;
        }
    }
    
    //construct our object graph
    DataTable data = SelectAllDataFromTable_OrderedByParentIDAscending();
    List<Page> topPages = new List<Page>();
    foreach(DataRow row in data.Rows)
    {
        Page page = new Page();
        page.Title = (string)row["Title"];
        page.ID = (int)row["PageID"];
        if(row["ParentID"] == null)
        {
            topPages.Add(page);
        }
        else
        {
            int parentID = (int)row["ParentID"];
            foreach(Page topPage in topPages)
            {
                Page parentPage = topPage.FindPage(parentID);
                if(parentPage != null)
                {
                    parentPage.Pages.Add(page);
                    break;
                }
            }
        }
    }
    
    //render to page
    public override void Render(HtmlTextWriter writer)
    {
        writer.WriteFullBeginTag("ul");
        foreach(Page child in topPages)
        {
            RenderPage(writer, child);
        }
        writer.WriteEndTag("ul");
    }
    
    private void RenderPage(HtmlTextWriter writer, Page page)
    {
        writer.WriteFullBeginTag("li");
        writer.WriteBeginTag("a");
        writer.WriteAttribute("href", "url");
        writer.Write(HtmlTextWriter.TagRightChar);
        writer.Write(page.Title);
        writer.WriteEndTag("a");
        if(page.Pages.Count > 0)
        {
            writer.WriteFullBeginTag("ul");
            foreach(Page child in page.Pages)
            {
                RenderPage(writer, child);
            }
            writer.WriteEndTag("ul");
        }
        writer.WriteEndTag("li");
    }
    

    【讨论】:

    • 最终目标是拥有可以用 css 设置样式的 ul 导航结构。您如何建议使用所有页面填充 Collection 以便我可以遍历它。递归 foreach(Page p in c.Pages) 创建字符串会效率低下吗?我没有很多行,但仍然...
    • @Milan 我提供的代码示例以这种方式填充结构。只要您使用像 StringBuilder 或 HtmlTextWriter 这样的缓冲写入器来生成最终输出,它就非常高效。请记住,ASP.NET 会通过大量 HTML 字符串来呈现页面!
    • @Milan 在您的原始问题中,您实质上是在描述递归。在 SQL2005 中没有很好的递归方式,通常最好直接获取一个数据集并将其组织成 .NET 端的对象层次结构,这就是我在上面所做的。
    • 抱歉,我没有看到完整的代码。我只看到了上半部分。你是我的英雄,谢谢。还有一个问题:您是否建议按原样使用,我的意思是使用渲染部分?我更愿意将它作为可以分配给 Literal.Text 或其他东西的字符串
    • +1 好答案@Milan 我添加了如何使用 rex 的渲染代码为我的答案获取字符串
    【解决方案3】:

    这应该让你开始。

    with x (pageID, title)
          as (
      select cast(title as varchar(100)),pageID
        from pages
       where parentID is null
       union all
      select cast(x.title||' - '||e.title as varchar(100)),
             e.pageID
        from pages e, x
       where e.parentID = x.pageID
      )
      select title as title_tree
        from x
       order by 1
    

    输出:

    TITLE_TREE
    Home
    Products
    Services
    Products - Category 1 
    Products - Category 2
    Products - Category 2 - Subcategory 1 
    Products - Category 2 - Subcategory 1 - Third Level Category 1
    Products - Category 2 - Subcategory 2
    

    【讨论】:

    • 感谢您的努力,但我得到了相同的解决方案,现在我被卡住了......我不知道如何处理这样的结构。
    【解决方案4】:

    您是否考虑过使用 SELECT ... FOR XML EXPLICIT 从 SQL Server 获取 XML 输出?您的数据似乎为此完美设置。

    举个例子:

    http://www.eggheadcafe.com/articles/20030804.asp

    如果你想继续,我可以举个例子。

    【讨论】:

    • XML 结合 XSLT 将其转换为 ul 列表,可能也不错。我只是不知道 xslt 转换如何影响性能。此外,如果我们生成良好的 XML,我们可以将输出与 Rex 的解决方案结合起来,并将 XML 直接反序列化为 .net 对象。对吗?
    • @Milan 我建议您在 sql server 之外进行处理,因为无论如何您都在获取整个结果集 - sql 负载较少
    【解决方案5】:

    RexM - 首先我必须声明我是一名前端开发人员,所以甚至无法接触到你的 C# 编码技能和知识。但是 - 我确实使用 Page 对象实现了您的解决方案并遇到了问题。是的,对不起,在这种情况下我是一个“pleaseSendMeTheCode”水蛭,但无论如何,我认为详细说明“错误”很重要。

    我正在构建一个使用嵌套 UL 来显示菜单项并允许用户根据需要重新排序菜单的站点。

    我的菜单有以下数据字段:pageID、parentID、pageOrder、pageTitle

    页面顺序是指页面在节点中出现的顺序。

    所以我对SelectAllDataFromTable_OrderedByParentIDAscending();的查询是:

    SELECT * FROM [pages] ORDER BY [parentID] ASC, [pageOrder] ASC
    

    然后我使用 jsTree 使菜单项可拖放。

    我重新订购了几页并发现了一个错误:

    说我的结构是这样的:

    home
      cars
        usa
          muscle cars
          suvs
        europe
      colours
      directions
        vertical
        horizontal
          up
          down
    

    如果我将“汽车”(以及它的所有子元素)移到“向下”中,“汽车”的子元素将不再显示在菜单中。这就是“错误”。

    我已经检查了 db 和 parentIDpageOrder 在“汽车”下都是正确的,我还尝试更改我的 SQL 查询,从头开始,各种测试直接在数据库上(上面所有的 jsTree 都关闭了,所以我可以看到基本的嵌套 UL) - 但没有成功。

    只是想知道,因为我已经看到其他论坛指向此页面以获取将分层 sql 数据转换为嵌套 UL 的解决方案,因此可能值得有人研究它。

    由于我的整个网站都是基于 Javascript 的使用,我现在已经实现了一个 Jquery.ajax 解决方案(非常糟糕的评论,在 my site 上)来构建嵌套的 UL,但正如我所说,只是标记作为潜在的问题。

    非常感谢您让我开始自己寻找解决方案!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-05-21
      • 1970-01-01
      • 1970-01-01
      • 2012-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多