自反+递归 实现评论的无限引用.docx

上传人:b****7 文档编号:10603359 上传时间:2023-02-21 格式:DOCX 页数:15 大小:30.65KB
下载 相关 举报
自反+递归 实现评论的无限引用.docx_第1页
第1页 / 共15页
自反+递归 实现评论的无限引用.docx_第2页
第2页 / 共15页
自反+递归 实现评论的无限引用.docx_第3页
第3页 / 共15页
自反+递归 实现评论的无限引用.docx_第4页
第4页 / 共15页
自反+递归 实现评论的无限引用.docx_第5页
第5页 / 共15页
点击查看更多>>
下载资源
资源描述

自反+递归 实现评论的无限引用.docx

《自反+递归 实现评论的无限引用.docx》由会员分享,可在线阅读,更多相关《自反+递归 实现评论的无限引用.docx(15页珍藏版)》请在冰豆网上搜索。

自反+递归 实现评论的无限引用.docx

自反+递归实现评论的无限引用

引言

大家每天都在看博客,发表评论,实现一个评论系统也是一名Web开发者的基本要求。

虽然评论只是一个很普通的功能,但是实现评论的引用,尤其是无限引用,却有一定的困难。

身为“网易工程队”的正规军,同时又作为一名程序开发人员,有必要向大家展示一下“盖楼”的方法。

效果预览:

NOTE:

本文使用基于业务对象(List)的筛选来进行引用列表的搜寻,对数据库仅进行了一次读取。

想也应该能想明白:

不管是初始评论还是包含引用的评论都属于同一文章下,一次读取该文章下的评论,进行列表搜寻就可以了,为什么要多次读取数据库!

  尽管如此,使用递归的效率依然是很低的,会进行频繁的方法调用,所以这篇文章的方法基本上只有实验价值,没有使用价值。

可以考虑在Comment表中建一个字段,QuoteContent,用来保存引用的内容,QuoteContent可以使用文中的方法来获得。

评论引用的“传统方法”

称之为“传统方法”,是因为这种方法很多的论坛都在采用,比如说蓝色理想。

做法是在点引用的时候,在回帖人的正文中,加入代码比如“[quote]引用内容...[/quote]回复正文”,然后在输出的时候,将UBB代码用正则表达式替换成HTML代码。

这种方法的好处是取出数据速度比较快,直接从数据库读出再送显就可以了。

缺点是写正则表达式比较麻烦,而且容易出错,比如一个[quote]的嵌套位置不正确,就会使表达式失效;还有就是会让数据库存储额外的数据(引用的内容也存储了)。

这种方法很多人可能都用过,我们就不讨论了,直接进入我们的正题。

自反关系的表结构

我们先介绍一下本文会频繁用到的两个术语:

∙初始评论:

表示这个评论没有引用其他任何评论。

∙引用评论:

表示这个评论包含对其他评论的引用。

数据表的结构是实现无限引用的前提,设计的不好就会很难实现。

我们先看一下建表的脚本:

CreateTableComment

   Id        intidentity(1,1)    NotNull,

   UserName  Varchar(200)         NotNull,

   Content   Varchar(2000)        NotNull,

   PostDate  DateTime           NotNullDefaultGetDate(),

   CommentId Int                  Null,     --外键,自反关系

   ArticleId Int                  NotNull

   Constraintpk_CommentPrimaryKey(Id)

  Constraintfk_Comment_CommentForeignKey(CommentId)ReferencesComment(Id)

∙Id:

评论的Id。

∙UserName:

通常情况下,这里是个int类型的UserId,引用一个User表,但在本文中简单起见,直接用Varchar类型。

∙Content:

评论的内容。

∙PostDate:

评论发表的时间。

这些我想都比较容易看懂,我们下来主要看CommentId和ArticleId:

CommentId:

这是一个关键字段。

这个字段引用了本身(Comment表)的Id字段,构成一个自反关系,它也是Comment表的外键。

当一个评论是初始评论时,它为Null;当一个评论是引用评论时,它为该评论所引用的评论的Id。

当我们需要获取某一个引用评论时,需要顺着它的CommentId纵深查找,直到找到CommmentId为Null的评论。

举例来说:

如果我想显示Id为17的评论,我先看它的CommentId是否为Null,如果为Null,那么它是一个初始评论,直接返回;如果不为Null,则寻找Id等于它的CommentId的评论,找到以后,再检查这个评论的CommentId,重复之前的过程,一直找到CommentId为Null的评论为止。

ArticleId:

这个大家应该比较熟悉了,它是评论所属的文章的Id。

NOTE:

这里我想说一下,如果想建立外键(自反是一种特殊的外键)。

那么外键所包含的字段要么为Null,要么为一个存在的主键。

我发现很多人不喜欢用Null,他们也不建外键,如果让他们来实现上面的表结构,当一个评论是初始评论时,他会给CommentId赋值为0,而不是Null。

虽然这样也没什么错,但我个人很不喜欢,不够规范。

页面实现

虽然我曾经花了不少时间学习Web标准,但是以后我不会再过分地分散精力了,我的文章也不会讲述Css和Web标准,所以这里只给出实现并略做一点说明。

尽管本文中评论部分的页面是动态生成的HTML,但是我们往往需要先设计一下HTML,编写好样式表,然后才去写程序,我们看下一个无限引用的HTML代码可能是什么样的。

在任意一个站点下创建一个页面NestedComment.aspx:

   

      2008-3-2416:

33:

49发表内蒙古网友

      

          

             

广州网友原贴:


                 向马XX同志荣升台湾省省长表示祝贺!

             

             四川网友原贴:


             四川人民发来贺电!

          

          陕西西安网友原贴:


          陕西网友发来贺电

      

      

内蒙网友发来贺电

   

   略...

   略...

   略...

   略...

可以看到,每一条评论都包含在一个cssClass为comment的div中,所有的div又包含在一个Id为commentHolder的div中,作为它们的容器。

我们之后要生成的代码,将会以上面的HTML代码作为格式和模板。

现在把它们注释掉,放置一个Repeater控件,代码如下:

   

Repeaterrunat="server"ID="rpComment"EnableViewState="false">

      

          <%#GetContent(Container.DataItem)%>

      

   

Repeater>

注意到将EnableViewState设为了False,以及在ItemTemplate中放置了一个方法GetContent(),并将当前绑定的项目作为参数传递了进去,这些我们在后置代码中会再讲到。

我们再看一下Css样式:

   *{margin:

0;padding:

0;}

   body{margin:

10px;font-size:

14px;font-family:

宋体}

   h1{font-size:

26px;margin:

10px015px;}

   #commentHolder{width:

540px;border-bottom:

1pxsolid#aaa;}

   .comment{padding:

5px8px;background:

#f8fcff;border:

1pxsolid#aaa;font-size:

14px;border-bottom:

none;}

   .commentp{padding:

5px0;}

   .commentp.title{color:

#1f3a87;font-size:

12px;}

   .commentpspan{float:

right;color:

#666}

   .commentdiv{background:

#ffe;padding:

3px;border:

1pxsolid#aaa;line-height:

140%;margin-bottom:

5px;}

   .commentdivspan{color:

#1f3a87;font-size:

12px;}

后置代码

Comment实体类

我们先创建一个实体类Comment,这个类用于映射数据库中的表Comment:

publicclassComment{

   privateintid;

   privatestringuserName;

   privatestringcontent;

   privateDateTimepostDate;

   privateintcommentId;

   privateintarticleId;

   publicintId{

      get{returnid;}

      set{id=value;}

   }

   publicstringUserName{

      get{returnuserName;}

      set{userName=value;}

   }

   publicstringContent{

      get{returncontent;}

      set{content=value;}

   }

   publicDateTimePostDate{

      get{returnpostDate;}

      set{postDate=value;}

   }

   publicintCommentId{

      get{returncommentId;}

      set{commentId=value;}

   }

   publicintArticleId{

      get{returnarticleId;}

      set{articleId=value;}

   }

}

评论的排序一般有两种:

一种是最新评论在最上面,一种是最新评论在最下面。

我个人比较喜欢最新评论在最上面这种,但是在引用评论中引用的评论列表肯定是最早的在最上面,所以我们需要实现列表的排序,一种是顺序,一种是倒序。

关于如何实现列表排序,在基于业务对象的排序中已经很详细的写明了,这里就不再讨论,只给出代码。

修改Comment类,添加如下代码:

publicclassComment{

   //...上面略

   publicstaticCommentComparerGetComparer(boolisAscending){

      returnnewCommentComparer(isAscending);

   }

   publicstaticCommentComparerGetComparer(){

      returnGetComparer(true);

   }

   //嵌套类,用于排序

   publicclassCommentComparer:

IComparer{

      privateboolisAscending;

      publicCommentComparer(boolisAscending){

          this.isAscending=isAscending;

      }

      publicintCompare(Commentx,Commenty){

          if(isAscending)

             returnx.Id.CompareTo(y.Id);

          else

             returny.Id.CompareTo(x.id);

      }

   }

}

获取评论列表:

GetList(intarticleId)方法

我们接着在代码后置类中添加一个方法,GetList(intarticleId),这个方法通常是根据文章Id(articleId)从数据库中获取这个文章下的所有评论,并返回一个List列表对象。

但是本文中,为了简单起见,我直接手动创建了这个列表对象(需要注意的是对于CommentId为Null的评论,我们将它的CommentId设为0,也可以使用int?

,这样int类型也可以设置为null,但我个人不大喜欢这样):

//应该来自于数据库,这里直接HardCoding了

//articleId是文章的Id,返回此文章下的所有评论

privateListGetList(intarticleId)

{

   Listlist=newList();

   Commentcmt1=newComment();

   cmt1.Id=15;               //评论Id

   cmt1.ArticleId=articleId;    //文章Id

   cmt1.CommentId=0;            //起始评论

   cmt1.Content="向马XX同志荣升台湾省省长表示祝贺!

";

   cmt1.PostDate=DateTime.Now.AddMinutes(-25); //25分钟前发表

   cmt1.UserName="广州网友";      //用户名

   

   Commentcmt2=newComment();

   cmt2.Id=16;

   cmt2.ArticleId=articleId;

   cmt2.CommentId=15;               //引用id为15的评论

   cmt2.Content="四川人民发来贺电!

";      

   cmt2.PostDate=DateTime.Now.AddMinutes(-19);

   cmt2.UserName="四川网友";

   Commentcmt3=newComment();

   cmt3.Id=17;

   cmt3.ArticleId=articleId;

   cmt3.CommentId=16;               //引用id为16的评论

   cmt3.Content="陕西人民发来贺电";    

   cmt3.PostDate=DateTime.Now.AddMinutes(-16);

   cmt3.UserName="陕西西安网友";

   

   Commentcmt4=newComment();

   cmt4.Id=18;

   cmt4.ArticleId=articleId;

   cmt4.CommentId=0;                //又一则起始评论

   cmt4.Content="希望台湾和平稳定发展。

";

   cmt4.PostDate=DateTime.Now.AddMinutes(-13);

   cmt4.UserName="黑龙江网友";

   

   Commentcmt5=newComment();

   cmt5.Id=19;

   cmt5.ArticleId=articleId;

   cmt5.CommentId=17;           //引用Id为17的评论

   cmt5.Content="宁夏人民发来贺电";

   cmt5.PostDate=DateTime.Now.AddMinutes(-8);

   cmt5.UserName="宁夏网友";

   

   Commentcmt6=newComment();

   cmt6.Id=20;

   cmt6.ArticleId=articleId;

   cmt6.CommentId=18;           //引用Id为18的评论

   cmt6.Content="支持楼上";   

   cmt6.PostDate=DateTime.Now.AddMinutes(-5);

   cmt6.UserName="加拿大网友";

   Commentcmt7=newComment();

   cmt7.Id=21;

   cmt7.ArticleId=articleId;

   cmt7.CommentId=17;           //引用Id为17的评论

   cmt7.Content="内蒙人民发来贺电";

   cmt7.PostDate=DateTime.Now.AddMinutes(-2);

   cmt7.UserName="内蒙古网友";

   list.Add(cmt1);

   list.Add(cmt2);

   list.Add(cmt3);

   list.Add(cmt4);

   list.Add(cmt5);

   list.Add(cmt6);

   list.Add(cmt7);

   returnlist;

}

填充Repeater控件,Page_Load事件代码

我们在Page_Load中调用GetList()方法,获取评论列表,将它按倒序排列,然后填充了Repeater控件:

protectedvoidPage_Load(objectsender,EventArgse)

{

   if(!

IsPostBack)

   {

      Listlist=GetList(16);      //获取ArticleId为16的所有评论

      list.Sort(Comment.GetComparer(false)); //倒序排列

      ViewState["List"]=list;          //设置ViewState

      rpComment.DataSource=list;

      rpComment.DataBind();

   }

}

注意到,我们使用ViewState保存了列表,一会还会看到,我们会从ViewState中还原列表,此时,Comment对象必须被标记为可串行化,修改Comment类,在顶部添加Serializable特性:

[Serializable]

publicclassComment{/*略*/}

获取输出:

GetContent()方法

接下来我们需要编写我们的核心方法GetContent,它嵌入在Repeater控件的ItemTemplate中,并接受Container.DateItem作为参数,而Container.DateItem代表的是Repeater控件显示的一个数据项,也就是一个Comment类型实例,再进一步就是数据库表中的一行。

递归调用:

AddComment()方法

在实现GetContent()方法之前,我们首先应该考虑如何根据一则评论,获取它所引用的所有评论。

如果我们需要编写一个方法,那么这个方法需要接收哪些参数:

1.方法肯定需要当前显示的评论(Comment),然后才能根据这个评论所引用的评论的Id,也就是它的CommentId属性,去逐层深入地搜寻其他的Comment。

2.我们应该有一个列表来保存搜寻到的Comment,我们管这个列表叫quoteList,它是List类型。

3.我们需要传递搜寻的对象,也就是当前文章下的所有评论列表,也是一个List类型。

再看看这个方法的流程应该是什么:

1.检查传递进来的评论,判断它的CommentId,如果为0,那么是起始评论,退出方法。

2.如果不是,搜索Id等于它的CommentId的评论;将找到的评论加入quoteList引用列表;然后再次调用方法,并传递找到的评论(递归调用)。

直到找出CommentId为0的评论为止。

现在我们来看一下AddComment()方法的代码:

//向quoteList中添加符合条件的Comment

protectedvoidAddComment(Listlist,ListquoteList,Commentcmt)

{

   if(cmt.CommentId!

=0)

   {

      Commentfind=list.Find(newPredicate(cmt.MatchRule));

      quoteList.Add(find);

      //递归调用,只要CommentId不为零,就加入到引用评论列表

      AddComment(list,quoteList,find); 

   }else

      return;

}

上面的参数list代表某一文章下的全部评论列表,cmt代表当前要显示的评论,quoteList代表当前要显示的评论所引用的评论列表。

列表搜寻:

Predicate(Tobj)委托

注意上面,在找寻Id等于当前评论的CommentId的评论,我使用了list.Find()方法。

如何进行列表的搜寻,我在基于业务对象的筛选中已经详细介绍了,这里只给出实现过程,不再进行讲述。

需要注意的是

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1