很多朋友对于无限分类网站源码分享和信息分类网站源码不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!
关于无限级分类
第一种方案:使用递归算法,也是使用频率最多的,大部分开源程序也是这么处理,不过一般都只用到四级分类。这种算法的数据库结构设计最为简单。category表中一个字段id,一个字段fid(父id)。这样可以根据WHEREid=fid来判断上一级内容,运用递归至最顶层。分析:通过这种数据库设计出的无限级,可以说读取的时候相当费劲,所以大部分的程序最多3-4级分类,这就足以满足需求,从而一次性读出所有的数据,再对得到数组或者对象进行递归。本身负荷还是没太大问题。但是如果分类到更多级,那是不可取的办法。这样看来这种分类有个好处,就是增删改的时候轻松了…然而就二级分类而言,采用这种算法就应该算最优先了。
第二种方案:设置fid字段类型为varchar,将父类id都集中在这个字段里,用符号隔开,比如:1,3,6这样可以比较容易得到各上级分类的ID,而且在查询分类下的信息的时候,可以使用:SELECT*FROMcategoryWHEREpidLIKE“1,3%”。
分析:相比于递归算法,在读取数据方面优势非常大,但是若查找该分类的所有父分类或者子分类查询的效率也不是很高,至少也要二次query,从某种意义看上,个人觉得不太符合数据库范式的设计。倘若递增到无限级,还需考虑字段是否达到要求,而且在修改分类和转移分类的时候操作将非常麻烦。暂时,在自己项目中用的就是类似第二种方案的解决办法。就该方案在我的项目中存在这样的问题,如果当所有数据记录达到上万甚至10W以上后,一次性将所以分类,有序分级的现实出来,效率很低。极有可能是项目处理数据代码效率低带来的。现在正在改良。
第三种方案:无限级分类—-改进前序遍历树那么理想中的树型结构应具备哪些特点呢?数据存储冗余小、直观性强;方便返回整个树型结构数据;可以很轻松的返回某一子树(方便分层加载);快整获以某节点的祖谱路径;插入、删除、移动节点效率高等等。带着这些需求我查找了很多资料,发现了一种理想的树型结构数据存储及操作算法,改进的前序遍历树模型(TheNestedSetModel)。原理:我们先把树按照水平方式摆开。从根节点开始(“Food”),然后他的左边写上1。然后按照树的顺序(从上到下)给“Fruit”的左边写上2。这样,你沿着树的边界走啊走(这就是“遍历”),然后同时在每个节点的左边和右边写上数字。最后,我们回到了根节点“Food”在右边写上18。下面是标上了数字的树,同时把遍历的顺序用箭头标出来了。
我们称这些数字为左值和右值(如,“Food”的左值是1,右值是18)。正如你所见,这些数字按时了每个节点之间的关系。因为“Red”有3和6两个值,所以,它是有拥有1-18值的“Food”节点的后续。同样的,我们可以推断所有左值大于2并且右值小于11的节点,都是有2-11的“Fruit”节点的后续。这样,树的结构就通过左值和右值储存下来了。这种数遍整棵树算节点的方法叫做“改进前序遍历树”算法。
表结构设计:
那么我们怎样才能通过一个SQL语句把所有的分类都查询出来呢,而且要求如果是子类的话前面要打几个空格以表现是子分类。要想查询出所有分类很好办:SELECT*FROMcategoryWHERElft>1ANDlft<18ORDERBYlft这样的话所有的分类都出来了,但是谁是谁的子类却分不清,那么怎么办呢?我们仔细看图不难发现如果相邻的两条记录的右值第一条的右值比第二条的大那么就是他的父类,比如food的右值是18而fruit的右值是11那么food是fruit的父类,但是又要考虑到多级目录。于是有了这样的设计,我们用一个数组来存储上一条记录的右值,再把它和本条记录的右值比较,如果前者比后者小,说明不是父子关系,就用array_pop弹出数组,否则就保留,之后根据数组的大小来打印空格。
以上内容引用出处:https://www.cnblogs.com/badboys/p/9945296.html
关于第三种设计的更多资料请点击查看原文,因为过于复杂(过重)被使用的频率不高。
引出痛点
无限级分类(父子)是一种比较常用的表设计,每种设计方式突出优势的同时也带来缺陷,如:
第一种方案:表设计中只有parent_id字段,写入数据方便,困扰:查询麻烦,许多使用了ORM的项目被迫使用SQL解决该场景;第二种方案:表设计中冗余子级id便于查询,困扰:添加/更新/删除的时候需要重新计算;第三种方案:表设计中存储左右值编码,困扰:同上;
第一种方案的设计最简单,本文后面的内容是在该基础上,使用FreeSql实现ToTreeList(内存加工树型)、AsTreeCte(实现递归向下/向上查询),满足大众日常使用。
关于FreeSql
FreeSql是功能强大的对象关系映射技术(O/RM),支持.NETCore2.1+或.NETFramework4.0+或Xamarin,以MIT开源协议托管于github,单元测试数量4528个,nuget下载量151K,支持MySql/SqlServer/PostgreSQL/Oracle/Sqlite/达梦/人大金仓/神州通用/Access;
源码地址:https://github.com/dotnetcore/FreeSql
作者说过:每一个功能代表他的一撮头发!
第一步:定义导航属性
FreeSql导航属性之中,有针对父子关系的设置方式,ToTreeList/AsTreeCte依赖该设置,如下:
publicclassArea\n{\n[Column(IsPrimary=true)]\npublicstringCode{get;set;}\n\npublicstringName{get;set;}\npublicstringParentCode{get;set;}\n\n[Navigate(nameof(ParentCode)),JsonIgnore]//JsonIgnore是json.net的特性\npublicAreaParent{get;set;}\n[Navigate(nameof(ParentCode))]\npublicList<Area>Childs{get;set;}\n}\n
关于导航属性
定义Parent属性,在表达式中可以这样:
fsql.Select<Area>()\n.Where(a=>a.Parent.Parent.Parent.Name==&34;)\n.First();\n
定义Childs属性,在表达式中可以这样(子查询):
fsql.Select<Area>()\n.Where(a=>a.Childs.AsSelect().Any(c=>c.Name==&34;))\n.First();\n
定义Childs属性,还可以使用【级联保存】、【贪婪加载】等等操作。
添加测试数据
fsql.Delete<Area>().Where(&34;).ExecuteAffrows();\nvarrepo=fsql.GetRepository<Area>();\nrepo.DbContextOptions.EnableAddOrUpdateNavigateList=true;\nrepo.DbContextOptions.NoneParameter=true;\nrepo.Insert(newArea\n{\nCode=&34;,\nName=&34;,\nChilds=newList<Area>(new[]{\nnewArea\n{\nCode=&34;,\nName=&34;,\nChilds=newList<Area>(new[]{\nnewArea{Code=&34;,Name=&34;},\nnewArea{Code=&34;,Name=&34;},\n})\n}\n})\n});\n
第二步:使用ToTreeList返回树型数据
配置好父子属性之后,就可以这样用了:
vart1=fsql.Select<Area>().ToTreeList();\nAssert.Single(t1);\nAssert.Equal(&34;,t1[0].Code);\nAssert.Single(t1[0].Childs);\nAssert.Equal(&34;,t1[0].Childs[0].Code);\nAssert.Equal(2,t1[0].Childs[0].Childs.Count);\nAssert.Equal(&34;,t1[0].Childs[0].Childs[0].Code);\nAssert.Equal(&34;,t1[0].Childs[0].Childs[1].Code);\n
查询数据本来是平面的,ToTreeList方法将返回的平面数据在内存中加工为树型List返回。
[\n{\n&34;:null,\n&34;:[\n{\n&34;:&34;,\n&34;:[\n{\n&34;:&34;,\n&34;:[],\n&34;:&34;,\n&34;:&34;\n},\n{\n&34;:&34;,\n&34;:[],\n&34;:&34;,\n&34;:&34;\n}\n],\n&34;:&34;,\n&34;:&34;\n}\n],\n&34;:&34;,\n&34;:&34;\n}\n]\n
第三步:使用AsTreeCte递归查询
若不做数据冗余的无限级分类表设计,递归查询少不了,AsTreeCte正是解决递归查询的封装,方法参数说明:
参数描述(可选)pathSelector路径内容选择,可以设置查询返回:中国->北京->东城区(可选)upfalse(默认):由父级向子级的递归查询,true:由子级向父级的递归查询(可选)pathSeparator设置pathSelector的连接符,默认:->(可选)level设置递归层级
通过测试的数据库:MySql8.0、SqlServer、PostgreSQL、Oracle、Sqlite、达梦、人大金仓
姿势一:AsTreeCte()+ToTreeList
vart2=fsql.Select<Area>()\n.Where(a=>a.Name==&34;)\n.AsTreeCte()//查询中国下的所有记录\n.OrderBy(a=>a.Code)\n.ToTreeList();//非必须,也可以使用ToList(见姿势二)\nAssert.Single(t2);\nAssert.Equal(&34;,t2[0].Code);\nAssert.Single(t2[0].Childs);\nAssert.Equal(&34;,t2[0].Childs[0].Code);\nAssert.Equal(2,t2[0].Childs[0].Childs.Count);\nAssert.Equal(&34;,t2[0].Childs[0].Childs[0].Code);\nAssert.Equal(&34;,t2[0].Childs[0].Childs[1].Code);\n//WITH&34;\n//as\n//(\n//SELECT0ascte_level,a.&34;,a.&34;,a.&34;\n//FROM&34;a\n//WHERE(a.&34;=&39;)\n\n//unionall\n\n//SELECTwct1.cte_level+1ascte_level,wct2.&34;,wct2.&34;,wct2.&34;\n//FROM&34;wct1\n//INNERJOIN&34;wct2ONwct2.&34;=wct1.&34;\n//)\n//SELECTa.&34;,a.&34;,a.&34;\n//FROM&34;a\n//ORDERBYa.&34;\n
姿势二:AsTreeCte()+ToList
vart3=fsql.Select<Area>()\n.Where(a=>a.Name==&34;)\n.AsTreeCte()\n.OrderBy(a=>a.Code)\n.ToList();\nAssert.Equal(4,t3.Count);\nAssert.Equal(&34;,t3[0].Code);\nAssert.Equal(&34;,t3[1].Code);\nAssert.Equal(&34;,t3[2].Code);\nAssert.Equal(&34;,t3[3].Code);\n//执行的SQL与姿势一相同\n
姿势三:AsTreeCte(pathSelector)+ToList
设置pathSelector参数后,如何返回隐藏字段?
vart4=fsql.Select<Area>()\n.Where(a=>a.Name==&34;)\n.AsTreeCte(a=>a.Name+&34;+a.Code+&34;)\n.OrderBy(a=>a.Code)\n.ToList(a=>new{\nitem=a,\nlevel=Convert.ToInt32(&34;),\npath=&34;\n});\nAssert.Equal(4,t4.Count);\nAssert.Equal(&34;,t4[0].item.Code);\nAssert.Equal(&34;,t4[1].item.Code);\nAssert.Equal(&34;,t4[2].item.Code);\nAssert.Equal(&34;,t4[3].item.Code);\nAssert.Equal(&34;,t4[0].path);\nAssert.Equal(&34;,t4[1].path);\nAssert.Equal(&34;,t4[2].path);\nAssert.Equal(&34;,t4[3].path);\n//WITH&34;\n//as\n//(\n//SELECT0ascte_level,a.&34;||&39;||a.&34;||&39;ascte_path,a.&34;,a.&34;,a.&34;\n//FROM&34;a\n//WHERE(a.&34;=&39;)\n\n//unionall\n\n//SELECTwct1.cte_level+1ascte_level,wct1.cte_path||&39;||wct2.&34;||&39;||wct2.&34;||&39;ascte_path,wct2.&34;,wct2.&34;,wct2.&34;\n//FROM&34;wct1\n//INNERJOIN&34;wct2ONwct2.&34;=wct1.&34;\n//)\n//SELECTa.&34;as1,a.&34;as2,a.&34;as5,a.cte_levelas6,a.cte_pathas7\n//FROM&34;a\n//ORDERBYa.&34;\n
更多姿势…请根据代码注释进行尝试
源码地址:https://github.com/dotnetcore/FreeSql
原文地址:https://www.cnblogs.com/FreeSql/p/13200935.html
好了,文章到此结束,希望可以帮助到大家。
