用 AppFramework 替换 Discuz!NT 的数据库访问层

(一)分析

DIscuz!NT 支持2种数据源,SqlServer和MSAccess,但其数据库访问层实际上已经支持了 MySQL,只是安装程序还未提供基于 MySQL  的。

 Discuz!NT采用了"页面类 -> 业务类 -> 数据库访问类 -> DbHelper -> 数据库"这样的分层方式。数据库访问类有1个大接口3个大实现。所谓大接口就是 IDataProvider 接口,定义了900 多个方法。3个大类实现了 IDataProvider 方法,用 partial class 的方式共分了7 个文件。这种大粒度的类设计方式本人觉得不利于以后维护。

3个大类分别为 MySql SqlServer Access 对 IDataProvider 的实现,输出3个DLL。在配置文件 DNT.config 里可以设置采用哪个实现。

总体来说分层还是比较严谨清晰的,但也有一些不合理之处:

1)把事务控制放到了数据库访问层。由于业务层无法控制事务的发起和终止,这样它们有一些大的业务实际上是在多个事务里处理的,容易因为一些意外导致数据不完整或不一致。我个人认为应该把数据库事务的发起和终止放在业务层。

2)数据库访问代码对SQL语句做了硬编码,类文件里SQL和C#语句遍地纠缠在一起,SQL层次结构不清晰,C#代码也不清晰,导致可维护性降低。

3)代码重复量太大。本来一些比较标准的SQL语句完全可以不需要多套实现,而Discuz基本上不管差异大小统统重写。其耐力令人钦佩,但法不可取。

4)过多使用存储过程,就连发帖子这样简单的事情,也写了个存储过程来实现,增加了数据库迁移工作量。

(二)修改

我的想法是用 AppFramework 跨数据库的特性,简单地重写数据库访问层。由于时间有限,我只花了3个晚上时间对 ForumManage.cs 进行了。

1、首先把配置文件 DBAccess.config 复制到 Discuz.Web 的 Config 目录下,设置了正确的数据库连接串。然后创建了一个新的工程 Discuz.Data.AppFramework,实现 IDataProvider。

2、在新工程里建了个 GenerateCode目录,放了7个.DaoGen文件和一个CodeGenPlugin.config。7个.DaoGen文件对应Discuz的7个模块。每个配置文件负责集中对模块内的表进行读写访问代码的生成。花了我1个小时的时间才把那些表分别归类配置到7个.DaoGen文件中,很快就生成了基本访问代码。例如:ForumManage.DaoGen内容:

<?xml version="1.0" encoding="gb2312" ?>
<!-- OR映射配置文件,请参考《AppFramework数据库访问中间件使用说明.doc》"入门 -> 安装-> 添加.DaoGen文件" 章节进行配置 -->
<Map>
  <Head
    Namespace="Discuz.Data.AppFramework.ForumManage"
    Using=""
    >
  </Head>

  <MapItem TableName="dnt_forumfields" PrimaryKey="fid" />
  <MapItem TableName="dnt_forumlinks" PrimaryKey="id" />
  <MapItem TableName="dnt_forums" PrimaryKey="fid" />
  <MapItem TableName="dnt_myposts" PrimaryKey="uid" />
  <MapItem TableName="dnt_mytopics" PrimaryKey="uid" />
  <MapItem TableName="dnt_posts1" PrimaryKey="pid" />
  <MapItem TableName="dnt_topicidentify" PrimaryKey="identifyid" />

  <statement id="GetForumIndexListTable">   
    <![CDATA[
    SELECT CASE WHEN DATEDIFF(n, lastpost, GETDATE())<600 THEN 'new' ELSE 'old' END AS havenew,
    dnt_forums.*, dnt_forumfields.*
    FROM dnt_forums LEFT JOIN dnt_forumfields ON dnt_forums.fid=dnt_forumfields.fid
      WHERE dnt_forums.parentid NOT IN (
        SELECT fid FROM dnt_forums
        WHERE status < 1 AND layer = 0)
      AND dnt_forums.status > 0
      AND layer <= 1
      ORDER BY displayorder
    ]
]>
  </statement>

  <statement id="GetForumIndexList">   
    <![CDATA[
    SELECT CASE WHEN DATEDIFF(n, lastpost, GETDATE())<600 THEN 'new' ELSE 'old' END AS havenew,dnt_forums.*, dnt_forumfields.* FROM dnt_forums LEFT JOIN dnt_forumfields ON dnt_forums.fid=dnt_forumfields.fid WHERE dnt_forums.parentid NOT IN (SELECT fid FROM dnt_forums WHERE status < 1 AND layer = 0) AND dnt_forums.status > 0 AND layer <= 1 ORDER BY displayorder
    ]]>
  </statement>
 
  <statement id="GetArchiverForumIndexList">   
    <![CDATA[
    SELECT dnt_forums.fid, dnt_forums.name, dnt_forums.layer, dnt_forumfields.viewperm FROM dnt_forums LEFT JOIN dnt_forumfields ON dnt_forums.fid=dnt_forumfields.fid WHERE dnt_forums.status > 0  ORDER BY displayorder
    ]]>
  </statement>

  <statement id="GetSubForum">   
    <![CDATA[
    SELECT CASE WHEN DATEDIFF(n, lastpost, GETDATE())<600 THEN 'new' ELSE 'old' END AS havenew,dnt_forums.*, dnt_forumfields.* FROM dnt_forums LEFT JOIN dnt_forumfields ON dnt_forums.fid=dnt_forumfields.fid WHERE parentid = #fid# AND status > 0 ORDER BY displayorder
    ]]>
  </statement>

  <statement id="GetForums">   
    <![CDATA[
    SELECT dnt_forums.*, dnt_forumfields.* FROM dnt_forums LEFT JOIN dnt_forumfields ON dnt_forums.fid=dnt_forumfields.fid ORDER BY displayorder
    ]]>
  </statement>
 
  <statement id="SetRealCurrentTopics">   
    <![CDATA[
    UPDATE dnt_forums SET curtopics = (SELECT COUNT(tid) FROM dnt_topics WHERE displayorder >= 0 AND fid=#fid#) WHERE fid=#fid#
    ]]>
  </statement> 

  <statement id="GetForumList">   
    <![CDATA[
    SELECT name, fid FROM dnt_forums WHERE dnt_forums.parentid NOT IN (SELECT fid FROM dnt_forums WHERE status < 1 AND layer = 0) AND status > 0 AND displayorder >=0 ORDER BY displayorder
    ]]>
  </statement> 
 
  <statement id="UpdateForum">   
    <![CDATA[
    UPDATE dnt_forums SET name=#name#,subforumcount=#subforumcount#,displayorder=#displayorder# WHERE fid=#fid#
    ]]>
  </statement>   
 
  <statement id="GetForumInformation">   
    <![CDATA[
    SELECT dnt_forums.*, dnt_forumfields.* FROM dnt_forums LEFT JOIN dnt_forumfields ON dnt_forums.fid=dnt_forumfields.fid WHERE dnt_forums.fid=#fid#
    ]]>
  </statement>   

  <statement id="SetForumsPathList">   
    <![CDATA[
    UPDATE dnt_forums SET pathlist=#pathlist#  WHERE fid=#fid#
    ]]>
  </statement> 

  <statement id="DeletePolls">   
    <![CDATA[
    DELETE FROM dnt_polls WHERE tid IN(SELECT tid FROM dnt_topics WHERE fid=#fid#)
    ]]>
  </statement> 
 
  <statement id="DeleteAttachments">   
    <![CDATA[
    DELETE FROM $TablePrefix$attachments WHERE tid IN (SELECT tid FROM $TablePrefix$topics WHERE fid=#fid#) OR pid IN (SELECT pid FROM #postname# WHERE fid=#fid#)
    ]]>
  </statement> 
   
  <statement id="GetUserByName">   
    <![CDATA[
    SELECT TOP 1 uid FROM $TablePrefix$users WHERE groupid<>7 AND groupid<>8 AND username=#username#
    ]]>
  </statement>   
   
  <statement id="UpdateForumfieldsModerators">   
    <![CDATA[
    UPDATE $TablePrefix$forumfields SET moderators=#moderators# WHERE fid =#fid#
    ]]>
  </statement>     
   
  <statement id="ClearForumfieldsModerators">   
    <![CDATA[
    UPDATE $TablePrefix$forumfields SET moderators=NULL WHERE fid =#fid#
    ]]>
  </statement>   
   
  <statement id="GetFidInForumsByParentID">   
    <![CDATA[
    SELECT fid FROM $TablePrefix$forums WHERE parentid=#parentid# ORDER BY displayorder ASC
    ]]>
  </statement>     
   
  <statement id="CombinationForums1">   
    <![CDATA[
    UPDATE $TablePrefix$topics SET fid=#targetfid#  WHERE fid=#sourcefid#
    ]]>
  </statement> 
   
  <statement id="CombinationForums2">   
    <![CDATA[
    SELECT COUNT(tid)  FROM $TablePrefix$topics WHERE fid IN($fidlist$)
    ]]>
  </statement> 

  <statement id="GetForumsByParentID">   
    <![CDATA[
    SELECT * FROM $TablePrefix$forums WHERE parentid=#parentid# ORDER BY DisplayOrder
    ]]>
  </statement>
 
  <statement id="GetTopForumIDs">   
    <![CDATA[
    SELECT TOP $statcount$ fid FROM $TablePrefix$forums WHERE fid > #lastfid#
    ]]>
  </statement>  
 
  <statement id="TopicTypeExists">       
    SELECT typeid FROM $TablePrefix$topictypes WHERE name=#typename#    
    <dynamic>
      <isNotNull property="not_typeid">
      <![CDATA[
        typeid<>#not_typeid#
      ]]>
      </isNotNull>
    </dynamic>
  </statement>  
 
  <statement id="TopicNameExists">   
    SELECT COUNT(1) FROM $TablePrefix$topicidentify WHERE name=#name#
    <dynamic>
      <isNotNull property="not_typeid">
        <![CDATA[
        typeid<>#not_typeid#
      ]]>
      </isNotNull>
    </dynamic>
  </statement>     
   
</Map>

3、修改 ForumManage.cs。Discuz的业务类向数据库访问类提交数据有些是用方法参数来传,有些是用业务实体来传,而数据访问层也多用DataTable 的方式向业务层返回数据。这导致 AppFramework 生成的实体类实际上最业务层用不到了,也用不到 ORMap 的一些优点,导致AppFramework的特能在这里没法全部发挥出来。

4、简单的数据库增删改的代码的修改,以下举几个例子,注释掉的部分是Discuz源代码,后面是基于 AppFramework 的实现。

/// <summary>
/// 添加友情链接
/// </summary>
/// <param name="displayorder">显示顺序</param>
/// <param name="name">名称</param>
/// <param name="url">链接地址</param>
/// <param name="note">备注</param>
/// <param name="logo">Logo地址</param>
/// <returns></returns>
public int AddForumLink(int displayorder, string name, string url, string note, string logo)
{
   
    //DbParameter[] parms = {
    //    DbHelper.MakeInParam("@displayorder", (DbType)SqlDbType.Int, 4, displayorder),
    //    DbHelper.MakeInParam("@name", (DbType)SqlDbType.NVarChar, 100, name),
    //    DbHelper.MakeInParam("@url", (DbType)SqlDbType.NVarChar, 100, url),
    //    DbHelper.MakeInParam("@note", (DbType)SqlDbType.NVarChar, 200, note),
    //    DbHelper.MakeInParam("@logo", (DbType)SqlDbType.NVarChar, 100, logo)
    //};
    //string sql = "INSERT INTO " + BaseConfigs.GetTablePrefix + "forumlinks] (displayorder, name,url,note,logo) VALUES (@displayorder,@name,@url,@note,@logo)";
    //return DbHelper.ExecuteNonQuery(CommandType.Text, sql, parms);

    DntForumlinksParam link = new DntForumlinksParam();
    link.Displayorder.Value = displayorder;
    link.Name.Value = name;
    link.Url.Value = url;
    link.Note.Value = note;
    link.Logo.Value = logo;

    using (IDBSession ss = DBSessionManager.Default.GetSession())
    {
        return _forumLinksDao.Insert(ss, link);
    }
}

/// <summary>
/// 删除指定友情链接
/// </summary>
/// <param name="forumlinkid"></param>
/// <returns></returns>
public int DeleteForumLink(string forumlinkidlist)
{
    //string sql = "DELETE FROM " + BaseConfigs.GetTablePrefix + "forumlinks WHERE id IN (" + forumlinkidlist + ")";
    //return DbHelper.ExecuteNonQuery(CommandType.Text, sql);

    using (IDBSession ss = DBSessionManager.Default.GetSession())
    {
        QueryFilter filter = new QueryFilter();
        filter.Add(DntForumlinksDef.ID_FieldName, DBOperator.In, forumlinkidlist);
        return _forumLinksDao.Delete(ss, filter);
    }   
}

/// <summary>
/// 更新指定友情链接
/// </summary>
/// <param name="id">友情链接Id</param>
/// <param name="displayorder">显示顺序</param>
/// <param name="name">名称</param>
/// <param name="url">链接地址</param>
/// <param name="note">备注</param>
/// <param name="logo">Logo地址</param>
/// <returns></returns>
public int UpdateForumLink(int id, int displayorder, string name, string url, string note, string logo)
{
    //DbParameter[] parms = {
    //    DbHelper.MakeInParam("@id", (DbType)SqlDbType.Int, 4, id),
    //    DbHelper.MakeInParam("@displayorder", (DbType)SqlDbType.Int, 4, displayorder),
    //    DbHelper.MakeInParam("@name", (DbType)SqlDbType.NVarChar, 100, name),
    //    DbHelper.MakeInParam("@url", (DbType)SqlDbType.NVarChar, 100, url),
    //    DbHelper.MakeInParam("@note", (DbType)SqlDbType.NVarChar, 200, note),
    //    DbHelper.MakeInParam("@logo", (DbType)SqlDbType.NVarChar, 100, logo)
    //};
    //string sql = "UPDATE " + BaseConfigs.GetTablePrefix + "forumlinks SET displayorder=@displayorder,name=@name,url=@url,note=@note,logo=@logo WHERE id=@id";
    //return DbHelper.ExecuteNonQuery(CommandType.Text, sql, parms);

    DntForumlinksParam link = new DntForumlinksParam();
    link.Displayorder.Value = displayorder;
    link.Name.Value = name;
    link.Url.Value = url;
    link.Note.Value = note;
    link.Logo.Value = logo;
    link.ID.Value = (short)id;

    using (IDBSession ss = DBSessionManager.Default.GetSession())
    {
        return _forumLinksDao.Update(ss, link);
    }

当然也可以用 SqlMap 的方式更新实体:

public int UpdateForum(int fid, string name, int subforumcount, int displayorder)
{
    //DbParameter[] parms = {
    //    DbHelper.MakeInParam("@fid", (DbType)SqlDbType.Int, 4, fid),
    //    DbHelper.MakeInParam("@name", (DbType)SqlDbType.NChar, 50, name),
    //    DbHelper.MakeInParam("@subforumcount", (DbType)SqlDbType.Int, 4, subforumcount),
    //    DbHelper.MakeInParam("@displayorder", (DbType)SqlDbType.Int, 4, displayorder)
    //};
    //string sql = "UPDATE " + BaseConfigs.GetTablePrefix + "forums SET name=@name,subforumcount=@subforumcount ,displayorder=@displayorder WHERE fid=@fid";
    //return DbHelper.ExecuteNonQuery(CommandType.Text, sql, parms);
    UpdateForum map = new UpdateForum();
    map.displayorder = displayorder;
    map.fid = fid;
    map.name = name;
    map.subforumcount = subforumcount;

    using (IDBSession ss = DBSessionManager.Default.GetSession())
    {
        return map.ExecCmd(ss);
    }
}

其中 UpdateForum  在 ForumManage.DaoGen 中的定义如下:

  <statement id="UpdateForum">   
    <![CDATA[
    UPDATE dnt_forums SET name=#name#,subforumcount=#subforumcount#,displayorder=#displayorder# WHERE fid=#fid#
    ]]>
  </statement>   

5、复杂的查询代码的修改。复杂的查询基本上都放在 ForumManage.DaoGen 里生成代码,然后用面向对象的方式来调用。

6、SQL的标准化。把SQL语句中所有的变量或对象名两端 "[" "]" 去掉。当然这样还是不彻底的,一些特有的不可移植的函数还要进一步想办法屏蔽。 

(三)总结

1、用 AppFramework修改后,只要是标准的SQL语句都不会有跨数据库移植的问题。如果全部访问层代码完成用 AppFramework 重写,可以少许多代码量。

2、用面向对象的方式访问数据库,使得SQL语句从C#程序中分离出来,变得清晰。

3、由于业务类不怎么使用业务实体,AppFramework 生成的代码在这里并没有得到充分利用,其性能优势也未发挥出来。

4、通过这次实践,IDBSession 增加了一些功能,特别是查询返回 IDataReader,使一些人的编程习惯可以得到延续。

5、性能测试发现,性能接近源代码,只降低1%、2% 左右。如果采用AppFramework生成的和DataReader的方法实体实现业务层,就可以免去不必要的业务数据传递开销和低效的 DataTable 操作,其性能必然大有提高。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值