打掉重練,n-tier 再續...

最近因為在看辦公室那邊的程式碼,雖然廠商寫的我覺得沒有說很好,但有些方法還是值得我學習

像在上面我學到一些有關 Wcf ,只是這個我本來就有排定要學(為了作個 Service 讓別的學校連資料用...
(用了 Wcf ,用別的語言寫的程式也能快速要資料,像 php 就可以透過 SoapClient 這樣...)

再來是那個專案還是照老樣子有玩 n-tier 架構...

因為我本來就有玩 n-tier ,只是 Linq 實在太好用,結果後面我都是用 Linq 在玩弄資料而已XD

不過最近因為在改寫之前寫的某專案,打算改版那邊的系統,所以又重新在玩... 來個舊瓶裝新酒(!?

因為資料庫的資料不見大概會很困擾,整個資料庫的架構我仍衍用舊的,所以主力都在網頁系統的改寫

那這次一次大改寫,直接把 Wcf 跟 n-tier 給塞進去,那這篇我打算介紹的是我寫的資料庫架構

 

我資料庫是拆成兩個專案檔的,一個是 WCT.MyData 及 WCT.MyData.SqlServer
所有專案方法的呼叫及回傳的資料結構全在 WCT.MyData 專案中定義,而 WCT.MyData.SqlServer 則是放著 DataAccess 的語法

當然,這兩個其實就分別涵蓋商業邏輯層 (WCT.MyData) 及 資料存取層 (WCT.MyData.SqlServer)
而之後我會再建構一個 Wcf 的服務來當介面層,就是一個三層式架構了 XD

這樣的架構拆的還蠻好的,但未來的事誰也說不準,說不定幾年後,我又想把他拆掉重練(抹汗~),當然,這是題外話(呵~)

在 WCT.MyData 中最主要的核心當然就是很有默契命名成 DbHelper 這類別,透過他實作各種有關資料庫的操作

其中 DbHelper 去 google 一下就能找到很多人的很多種寫法,而我不得不說,我的專案很多的構想其實是來自我以前改 Discuz!NT 的經驗
他是一個很完整的專案,提供我很多想法,但有些東西我根本用不掉也還得踢除,像是他有平衡的機制,但我根本就只有一台 SqlServer ,所以這個就是不必要的東西了

而不一樣的地方是我的 DbHelper 並不會只丟回像 DbDataReader 或 DataTable 等,我有透過一些語法回傳一個完整的類別,所以型態那些都會是明確的

那說這麼多,還是來看一些些相關的程式碼吧,其中 DbHelper 因為太長我就不丟上來了,你可以 google 別人的,大致上都類似(!?

那我的系統在 MyData 那專案檔裡還放置了像資料表結構的定義,其中在上面都有加上 DataContract 定義,本來這個我是想在 Wcf 那邊定義,但後來就甘脆全整併到這邊了,以下是舉例的 Source Code

[DataContract]
public class Test {
    [DataMember]
    public int id { get; set; }

    [DataMember]
    public string Name { get; set; }
}

那透過這樣的撰寫就得到一個類似資料表的結構了,另外我有定義一個結構來放置資料的 DbType, dbLength 及 資料的 Type

    public class RespositoryFieldType
    {
        public DbType dbType { get; set; }
        public int dbLength { get; set; }
        public Type type { get; set; }

        public RespositoryFieldType(string Name, Type type, DbType dbType, int dbLength)
        {
            this.type = type;
            this.dbType = dbType;
            this.dbLength = dbLength;
        }
    }

會需要這樣的結構是我想撰寫一個類似 jqGrid 的 Filter 功能,讓整個類別我可以透過 AddFilter 的方式新增想要 Where 出來的資料,最後再透過 Select 函數將他新增的 Filter 後的結果一次取出來,而藉助 

    public abstract class RepositoryExtens<T>
        : IExtraRepository<T>
    {
        protected Dictionary<string, RespositoryFieldType> m_FieldType = Utils.GetRespositoryFieldType(typeof(T));
        protected Dictionary<string, List<object>> m_Filter = new Dictionary<string, List<object>>();
protected Dictionary<string, WCT.MyData.Enumerations.OrderDirection> m_OrderBy = new Dictionary<string, Enumerations.OrderDirection>();

        #region [ Filter 擴展 ]
        public void AddFilter(string key, object value)
        {
            if (!m_FieldType.ContainsKey(key) ||
                !m_FieldType[key].type.Equals(value.GetType()))
                return;
            if (m_Filter.ContainsKey(key))
            {
                m_Filter[key].Add(value);
            }
            else
            {
                m_Filter.Add(key, new List<object>() {
                    value
                });
            }
        }
        #endregion
        #region [ OrderBy 擴展 ]
        public void AddOrderBy(string key, Enumerations.OrderDirection orderDirection)
        {
            if (m_OrderBy.ContainsKey(key))
            {
                m_OrderBy[key] = orderDirection;
            }
            else
            {
                m_OrderBy.Add(key, orderDirection);
            }
        }
        #endregion
    }

這是我撰寫出來的抽象類別,透過這類別來實現動態加 Filter 及 Sort 到我的 DataAccess 層,最後他在 Select 時透過呼叫裡面的 m_Filter 字典就知道有哪些要被加到 Where 裡,而透過 m_OrderBy 則能取出要用哪個欄名排序,並且得到排序的幂次

最後透過在 WCT.MyData.SqlServer 專案中我撰寫的 Extensions 來進行 ToWhere 及 OrderBy 的合併,語法如下

    public static class SqlExtensions
    {
        /// <summary>
        /// 透過 filter 變數產生 Where 字串
        /// </summary>
        /// <param name="filter"></param>
        /// <param name="fieldType"></param>
        /// <param name="varCount"></param>
        /// <param name="prams"></param>
        /// <returns></returns>
        public static string ToWhere(this Dictionary<string, List<object>> filter, ref Dictionary<string, RespositoryFieldType> fieldType, ref int varCount, ref List<DbParameter> prams)
        {
            StringBuilder sql = new StringBuilder();
            RespositoryFieldType m_fieldType;
            bool NotFirst = false;

            if (filter.Count > 0)
            {
                sql.Append(" WHERE ");
                foreach (var kv in filter)
                {
                    if (kv.Value.Count == 0)
                        continue;

                    m_fieldType = fieldType[kv.Key];

                    if (NotFirst)
                    {
                        sql.Append(" AND ");
                    }
                    else
                    {
                        NotFirst = true;
                    }

                    if (kv.Value.Count > 1)
                    {
                        sql.AppendFormat("[{0}] In (", kv.Key);
                        for (int i = 0; i < kv.Value.Count; i++)
                        {
                            if (i > 0)
                                sql.Append(",");
                            sql.AppendFormat("@Var{0}", varCount);
                            prams.Add(DbHelper.MakeInParam(string.Format("@Var{0}", varCount), m_fieldType.dbType, m_fieldType.dbLength, kv.Value[i]));

                            varCount++;
                        }
                        sql.Append(")");
                    }
                    else
                    {
                        sql.AppendFormat("[{0}] = @Var{1}", kv.Key, varCount);
                        prams.Add(DbHelper.MakeInParam(string.Format("@Var{0}", varCount), m_fieldType.dbType, m_fieldType.dbLength, kv.Value[0]));
                        varCount++;
                    }
                }
            }
            return sql.ToString();
        }

        /// <summary>
        /// 透過 orderBy 變數產生 order by 字串
        /// </summary>
        /// <param name="orderBy"></param>
        /// <returns></returns>
        public static string ToOrderBy(this Dictionary<string, WCT.MyData.Enumerations.OrderDirection> orderBy)
        {
            StringBuilder sql = new StringBuilder();
            bool isFirst = true;

            if (orderBy.Count > 0)
            {
                sql.Append(" ORDER BY ");
                foreach (var kv in orderBy)
                {
                    if (!isFirst)
                        sql.Append(",");
                    sql.AppendFormat("[{0}]", kv.Key);
                    if (kv.Value == Enumerations.OrderDirection.Desceding)
                        sql.Append(" DESC");
                    isFirst = false;
                }
            }
            return sql.ToString();
        }
    }

其實寫了很多 Code 才達到部份 Linq 能達到的功能,但是能自己實作出這樣的程式其實很有成就感

不過如果是很趕的專案還是用 Linq 吧,用 n-tier 只會拉長開發的時程,但是就長遠而言,由於可以把工作切出去,在大型團隊上開發我想還是必要的

只是像我開發的專案很多時候都是一人樂,那 n-tier 其實沒啥幫助(哈~

礙於我不想一開起來就滿滿的文字,其實我的 code 省略了很多,只有放一些比較關鍵的程式碼上來

如果對文章有任何問題也歡迎一起討論!

2014/6/12 隨筆:最近練習了 Expression Tree ,其實這個很容易透過 Expression Tree 作衍伸XD

然後文章請勿轉載,這畢竟是我花了不少時間的心血結晶,謝謝!

 

留言

這個網誌中的熱門文章

DB 資料庫呈現復原中

Outlook 刪除大量重覆信件

[VB.Net] If vs IIf ,兩者的差異