1. 程式人生 > 其它 >實體類的變形【2】—— 行列轉換

實體類的變形【2】—— 行列轉換

    上次說了一下在網頁裡面顯示列表資料的情況,這個應用範圍太小了,新增、修改怎麼辦呢?網站的後臺管理、OA、CRM等怎麼辦?還是這樣處理顯然是不行的。

我們還是看一個小例子,這回是資料庫設計的。

假設我們要做一個小學的成績單,設計一個成績表

小學生成績表

欄位:學生名稱、語文成績、數學成績、美術成績等。

小學裡的課程是有限的,就那麼幾個,都作為欄位放在表裡面就ok了。

如果我們現在要做一箇中學的成績單呢?物理、化學、生物、地理、歷史課程增加了不少,還是往用往表裡面增加欄位的方式嗎?好像也勉強可以。

如果我們現在要做一個大學的成績單呢?還用這種方法就絕對不行了。換一個方法吧,行列轉換一下。

大學成績單

欄位:學生ID、課程ID、成績

這樣三個欄位搞定,當然還可以再根據情況增加一個欄位,比如系ID、專業ID等,沒有真正做過,只是猜想。這樣學生和課程就可以隨意組合了。當然他有一個很大的缺點,就是顯示成績單的時候有點麻煩,但是這個不在本次討論的範圍內。

     好了,回到程式的世界裡面。上次我們定義了一個類用來顯示列表資料,

public class Group_topic_Show
 {
  public string Topic;  //話題名稱
  public string TopicURL;  //話題的連線地址
  public string Group;  //話題所屬小組的名稱,來自“小組”表
  public string GroupURL;  //小組的連線地址
  public string View;   //迴應/瀏覽
  public string SendDate;  //話題發表日期
  public string Author;  //作者姓名,也是來自另一個表
  public string AuthorURL; //作者的連線地址
   
 }

     假設我們要往這個表裡面新增資料怎麼辦呢?

     等等這個類是用於顯示資料的,新增的時候還可以使用這個類嗎?好像不行了吧,要換成下面的形式

public class Group_topic
 {
  public string Topic;  //話題名稱
  public string TopicURL;  //話題的連線地址
  public string GroupID;  //話題所屬小組的ID
  public string View;   //迴應/瀏覽,這個可以不填(利用資料庫的預設值),或者在程式裡填0。
  public string SendDate;  //話題發表日期,這個一般也是取預設值
  public string AuthorID;  //作者姓名ID
  public string Content;  //主題內容
  //其他欄位
 }

     這就是第一個問題:資料庫裡有三個表(主題、分組、會員),發表主題的時候往一個表裡面寫資料就可以了,但是顯示主題的時候還需要兩外兩個表裡面的資料,那麼這時候實體類如何定義呢?我這裡想到了兩種方案:

第一種方案:一個表只對應一個實體類,三個表就會有三個實體類,這樣新增的時候沒有什麼問題,顯示的時候就要用類的關係的方式聯絡到一起,具體怎麼做我還不知道呢。      這樣做有一個明顯的缺點,就是在顯示列表的時候,主題的內容也會被載入進來,但是在列表的時候根本就不需要,由於這個欄位裡面的資料量比較大,所以就顯得很浪費資源!

第二種方案:新增的時候用一個實體類 Group_topic,顯示的時候用另一個實體類 Group_topic_Show,就像上面定義的兩個實體類。但是這個也有很明顯的缺點,好多的欄位名重複出現!如果這時候修改了欄位名的話,修改量就會增倍!

     這個就是我不想用三層的一個原因,總是要想實體類和資料表如何對應,很煩,左也不是,右也不是,不知道如何來做,那就乾脆不用三層的這種形式吧。看了亞同學的帖子,好像他也有這樣的問題?!我還以為只有我一個人有呢。

     好了先不說這個問題了,我們繼續。假設我們定義了一個 Group_topic類,要用這個類來實現新增、修改資料庫。顯示的問題先不考慮。

一般的步驟:

1、UI裡面放置控制元件 2、取值,給實體類賦值 3、驗證,邏輯處理 4、拼接SQL語句,或者設定儲存過程的引數 5、提交給資料庫

我見過的一種方式是這樣的,資料層裡寫這樣的程式碼

sql = "insert into Group_topic (Topic,GroupID,Author) values (@Topic,@GroupID,@Author)";

//新增儲存過程的引數。 cmd.Parameters.Add("@GroupID", SqlDbType.Int).Value = fam.GroupID;  //自動ID 1:為系統管理應用

這樣寫,我覺得挺繁瑣的,為什麼說繁瑣呢?我們看看欄位名出現了幾次?(Topic,GroupID,Author)這裡出現了一次,(@Topic,@GroupID,@Author)" 這裡是第二次,("@GroupID", 又是一次,fam.GroupID 這是第四次了!

就是一個欄位名呀,出現了四次,煩不煩呀,修改的時候,只是資料層就有四個地方,恐怖!雖然你可以使用程式碼生成器搞定,但是當需求有變化的時候呢,程式碼生成器可以很好的解決嗎?

“嘻嘻哈哈”極力推薦我看一個ASP.NET許可權管理系統的原始碼,三層寫的,上面的給引數賦值的語句就是從“許可權管理系統”copy出來的。看了之後有幾點心得:

1、他的程式碼真的像我上面寫的過程,基本差不多。 2、我才知道為什麼程式碼生成器會這麼火,因為沒有程式碼生成器的幫助,根本就沒有辦法寫三層的程式碼。 3、抽象在哪裡?相似的函式太多了。

public override int sys_ApplicationsInsertUpdate(sys_ApplicationsTable fam)
        {
            int rInt = 0;
            using (SqlConnection Conn = GetSqlConnection())
            {
                SqlCommand cmd = new SqlCommand("sys_ApplicationsInsertUpdateDelete", Conn);
                cmd.CommandType = CommandType.StoredProcedure;
                //設定引數
                cmd.Parameters.Add("@DB_Option_Action_", SqlDbType.NVarChar).Value = fam.DB_Option_Action_; //操作方法 Insert:增加 Update:修改 Delete:刪除 Disp:顯示單筆記錄
                cmd.Parameters.Add("@ApplicationID", SqlDbType.Int).Value = fam.ApplicationID;  //自動ID 1:為系統管理應用
                cmd.Parameters.Add("@A_AppName", SqlDbType.NVarChar).Value = fam.A_AppName;  //應用名稱
                cmd.Parameters.Add("@A_AppDescription", SqlDbType.NVarChar).Value = fam.A_AppDescription;  //應用介紹 
                cmd.Parameters.Add("@A_AppUrl", SqlDbType.VarChar).Value = fam.A_AppUrl;  //應用Url地址
                Conn.Open();
                rInt = Convert.ToInt32(cmd.ExecuteScalar());
                cmd.Dispose();
                Conn.Dispose();
                Conn.Close();
            }
            return rInt;
        }

        /// <summary>
        /// 返回sys_ApplicationsTable實體類的ArrayList物件
        /// </summary>
        /// <param name="qp">查詢類</param>
        /// <param name="RecordCount">返回記錄總數</param>
        /// <returns>sys_ApplicationsTable實體類的ArrayList物件</returns>
        public override ArrayList sys_ApplicationsList(QueryParam qp, out int RecordCount)
        {
            PopulateDelegate mypd = new PopulateDelegate(base.Populatesys_Applications);
            return this.GetObjectList(mypd, qp, out RecordCount);
        }

像這樣形式的函數出現了好多好多,估計專案越大,這樣的函式就會越多!而且為了支援多種資料庫,他寫了三個檔案SqlDataProvider.cs、OracleDataProvider.cs、AccessDataProvider.cs,這三個檔案裡的程式碼也都差不多。為什麼要一邊一邊的寫類似的函式呢?就是因為實體類的屬性是不一樣的!

     有一點很奇怪,SQL SERVER使用儲存過程,而ACCESS確實用引數化的SQL語句,為什麼不都是用引數化的SQL語句呢?難道在SQL SERVER的儲存過程裡面還要做一些判斷嗎?

相似的程式碼重複出現,“重用”在哪裡呢?!

     為了解決這樣的問題,有些同學提出來了使用反射,估計是使用反射地方法把屬性名稱反射成欄位名稱,再拼接引數化的SQL語句,然後再新增儲存過程的引數。(不知道引數型別是如何得到的?)      這樣倒是可行,但是效率上有一點點損耗,從原理上來說也是挺“鬱悶”的,我們在編碼的時候用字元換的形式定義了實體類的屬性,然後編譯,變成了一種形式,然後用的時候在通過反射,再把這種形式變回字串的形式,繞了一圈,為什麼繞圈圈呢?

     所以我給實體類變一下形式,“行列轉換”了一下。

class ColInfo()
{
 public string ColName;
 public string ColValue;
}

上面的屬性就變成了這樣

ColInfo tmpColInfo = new ColInfo(); tmpColInfo.ColName = "Topic"; tmpColInfo.ColValue ; //這個在定義的時候是沒法賦值的,需要在UI裡面賦值。

     然後再放在一個集合裡面,一個有屬性(欄位)組合而成的“類”(集合)就誕生了。這樣我們就做到了以屬性(欄位)為最小單位進行隨意組合,也可保證欄位名在程式裡面只出現一次,這樣就大大減少了修改的機會。

儲存資料的時候,程式碼也可以進一步的抽象,抽象成一個函式來處理。不用一個類寫一套函數了,

     只有兩個屬性還不夠,欄位型別怎麼辦呢?我們在擴充一下吧,加幾個屬性,

/// <summary>
    /// 欄位的基本資訊的描述
    /// </summary>
    public class ColumnsInfoBase        
    {
        #region 欄位的基本資訊的描述
        /// <summary>
        /// 配置資訊裡面的欄位的標識
        /// </summary>
        public int ColumnID = 0;

        /// <summary>
        /// 資料庫裡的欄位名稱
        /// </summary>
        public string ColSysName = "";

        /// <summary>
        /// 顯示給客戶看的名稱
        /// </summary>
        public string ColName = "";

        /// <summary>
        /// 欄位型別,int nvarchar 等
        /// </summary>
        public string ColType = "";

        /// <summary>
        /// 欄位大小
        /// </summary>
        public Int32 ColSize = 0;

        /// <summary>
        /// 欄位值
        /// </summary>
        public string ColValue;

        
        /// <summary>
        /// 查詢方式
        /// </summary>
        public Int32  FindKind = 0;
        #endregion

    }

     這樣呢,我們就可以根據這些資訊來拼接引數化的SQL語句和儲存過程的引數了。

對了,集合的方式還沒有寫呢,

Dictionary<int, ColumnsInfoForm>  dic_BaseCols = new Dictionary<int, ColumnsInfoForm>();
ColumnsInfoForm info;

            foreach (DataRow dr in dt.Rows)
            {
                info = new ColumnsInfoForm();

                info.ColumnID = Int32.Parse(dr["ColumnID"].ToString());
                info.ColSysName = dr["ColSysName"].ToString();
                info.ColName = dr["ColName"].ToString();
                info.ColType = dr["ColType"].ToString();
                info.ColSize = (Int32)dr["ColSize"];
                info.FindKind = (Int32)dr["FindKind"];

                dic_BaseCols.Add(info.ColumnID, info);

            }

     為什麼 key 不用欄位名而用欄位ID呢?因為欄位名是“不可靠”的,欄位名是會變的呀,變了怎麼辦呀?就會有修改程式碼的可能。當然也不是說欄位ID是絕對不會變化的,欄位ID只會被“刪除”,而不會被修改,欄位ID要比欄位名穩定很多,因為欄位ID是與業務邏輯一點關係都沒有的,而欄位名多少和業務邏輯是有關聯的。

     不過這樣“實體類”就由裝載資料變成了對欄位的描述,有了這些資訊,我們就可以用作拼接SQL語句(引數化的或者非引數化的),設定儲存過程的引數,加上查詢方式,就可以拼接“查詢條件”,就是SQL語句的Where後面的部分。

一個函式就可以搞定了,不用寫這麼多的類似的函數了!我們可以繼續進行擴充套件,可以描述欄位在UI裡的表現形式,比如用什麼控制元件(文字框、下拉列表框還是複選框等),驗證方式等。還有就是表單的佈局。可以描述列表的表現形式,那個欄位在前面,那個在後面,是否需要格式化(Format)等。

     其實這個就是我的表單控制元件、查詢控制元件、顯示資料的控制元件裡面使用的一種載體。一開始寫的時候還沒有意識到,寫完了之後才發現,自己居然寫成了這種形式。當然在我的控制元件裡面,類的載入(例項化)都是依據配置資訊來做的。

優點: 1、以欄位為最小單位。這樣資料表裡面的欄位,在“程式碼”裡面只會出現一次,然後可以讓他們靈活的組合,不管是那個表裡的欄位都可以組合在一起,放在一個集合裡面。

2、需要欄位名的時候,使用 屬性就可以了,不用反射了。

     第一個優點是相對於現在三層裡面的實體類來說的,三層裡的實體類都是以表為最小單位的,屬性只能是類裡面的一部分,不能獨立存在,這樣就很不靈活,這就是第一個問題的由來。