上一文中讲了带编号(或说是路径)的树状目录结构基本操作。谢谢有朋友提醒SQL Server2008以上版本可以采用HierarchyId类型字段,也很希望能看到阐述这方面最佳实践的文章。
理论联系实践,再提高理论认识,这是我一直追求的一个良性循环。本文将目录结构在实践中的一个应用,其实和结构存储方式倒没直接关系。
先请大家看个截图,这是一个部门选择菜单控件。
这是一个纯CSS实现的多层级下拉菜单,相对来说在网页上较少见。当点击选择一个部门时,旁边的文本框会显示该部门的各层级名称,并将该部门编号记录到一个隐藏域中,用于提交查询。
这个控件和遍历目录下的文件一样,用递归方式写入HTML代码,应该算基本的编程技术了。
public partial class SectionMenu : System.Web.UI.UserControl { protected StringBuilder HtmlBuilder = new StringBuilder(); protected void Page_Load(object sender, EventArgs e) { WriteSectionMenu(null); } public void WriteSectionMenu(string parentID) { var topSections = SectionHelper.GetSubSections(parentID, true); //读取一级子部门 if (topSections.Count < 1) return; HtmlBuilder.Append("<ul>"); foreach (var item in topSections) { HtmlBuilder.AppendFormat("<li><a href=\"{0}\">{1}</a>", item.SectionNo, item.Name); WriteSectionMenu(item.SectionNo); HtmlBuilder.Append("</li>"); } HtmlBuilder.Append("</ul>"); } }
菜单前端实现参考示例(单html文件):下载。
这种下拉菜单方便之处一目了然,缺点是导致页面体积显著增大,像示例那种,一两百个部门,只增加了十几KB还好,更多的话就令人难以接受了。解决的方案我认为只有一种—缓存。
缓存是B/S开发中的最深的一门学问,没有之一。一个男人,如果能在老妈和老婆中间左右逢源,便是一个不算失败的男人;如果男人还能照顾到子女的平衡,必须是一个优秀的男人。同样,在Web开发中,能灵活运用数据缓存和输出缓存的,便是一个不错的程序员;若能考虑充分利用客户端缓存,应该就是个杰出的程序员;而如果他能设计全局缓存和节点缓存的话,我想那是大师级人物吧。
我想提供一下Server端的数据缓存和输出缓存,以及Client的浏览器缓存方案,可惜我还没实现完整,放在下文再讲吧。有朋友似乎对我上文中的理论不太认可,我提供两段Linq To SQL代码,实现的功能相同,都是查找某部门员工的请假记录(Leave表),但存储结构不同。
记录父级编号存储方式:
var sectionNo = "00"; //部门编号 //所有层级子部门键人集合 var subSections = SectionHelper.GetSubSections(sectionNo, false).Select(s=>s.Id).ToList(); var result = from l in db.Leaves join p in db.PersonInfoes on l.Alias equals p.Alias where subSections.Contains(p.SectionId) select l; query = (result as ObjectQuery).ToTraceString();
生成的SQL语句:
SELECT [Extent1].[ID] AS [ID], [Extent1].[Type] AS [Type], [Extent1].[BeginTime] AS [BeginTime], [Extent1].[EndTime] AS [EndTime], [Extent1].[Alias] AS [Alias], [Extent1].[AssistantID] AS [AssistantID], [Extent1].[PostTime] AS [PostTime] FROM [Affair].[Leave] AS [Extent1] INNER JOIN [Firm].[PersonInfo] AS [Extent2] ON [Extent1].[Alias] = [Extent2].[Alias] WHERE [Extent2].[SectionId] IN (1,2,5,7,13,24,……,N)
记录路径编号存储方式:
var sectionNo = "00"; //部门编号 var result = from l in db.Leaves join p in db.PersonInfoes on l.Alias equals p.Alias where p.SectionNo.IndexOf(sectionNo) == 0 select l; Console.WriteLine((result as ObjectQuery).ToTraceString());
生成的SQL语句是:
SELECT [Extent1].[ID] AS [ID], [Extent1].[Type] AS [Type], [Extent1].[BeginTime] AS [BeginTime], [Extent1].[EndTime] AS [EndTime], [Extent1].[Alias] AS [Alias], [Extent1].[AssistantID] AS [AssistantID], [Extent1].[PostTime] AS [PostTime] FROM [Affair].[Leave] AS [Extent1] INNER JOIN [Firm].[PersonInfo] AS [Extent2] ON [Extent1].[Alias] = [Extent2].[Alias] WHERE 0 = (( CAST(CHARINDEX(@p__linq__0, [Extent2].[SectionNo]) AS int)) - 1)
孰优孰劣,大家可以比较下生成的SQL语句,应该会有一些概念。