程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> 用 AppFramework 替換 Discuz!NT 的數據庫訪問層

用 AppFramework 替換 Discuz!NT 的數據庫訪問層

編輯:.NET實例教程

(一)分析

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 TableNam

$False$

e="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[
&nbsp;   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">
     &nbsp;  <![CDATA[
        typeid<>#not_typeid#
      ]]>
      </isNotNull>
    &lt;/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 操作,其性能必然大有提高。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved