打造輕量級的實體類數據容器

裏有三個關鍵詞:輕量級實體類數據容器,還有一個潛在的關鍵詞:通用。這幾個名詞之間有什麼聯繫呢?

    一般來說,操作實體類往往伴隨着一個實體類集合,而這些集合就是實體類的容器,在這裏我將“容器”視作一個比集合更廣泛的概念,例如Entity Framework做了一個重量級的容器ObjectContext,用於與作爲對象(這些對象爲 EDM 中定義的實體類型的實例)的數據進行交互。

    實體類與容器沒有必然關係,例如DataSet也是一個容器,它存儲並操作DataTable,而DataTable也可以看做是各個單元格數據的容器...

    但是,這些“數據容器”還是顯得比較重量級,裏面有太多要交互的子對象,爲此我在PDF.NET(PWMIS數據開發框架)中定義了一個非常輕量級的實體數據容器,它存儲數據的原則很簡單,就是一個object[][],外加一個對應的字段名稱數組,其它諸如表的元素據等信息都沒有存儲,也就是下面程序中的3個私有對象:

 

複製代碼
    /// <summary>
    
/// 實體數據容器
    
/// </summary>
    public class EntityContainer
    {
        private string[] fieldNames;
        private List<object[]> Values;
        private object[] currValue;

    }
複製代碼

 

實體容器接收一個DataReader對象,將其中的數據讀入Values 數組,下面是相應的方法代碼:

 

複製代碼
        /// <summary>
        
/// 執行DataReader查詢,並將查詢結果緩存
        
/// </summary>
        
/// <param name="reader">數據閱讀器</param>
        
/// <returns>結果行數</returns>
        public int Execute(IDataReader reader)
        {
            List<object[]> list = new List<object[]>();
            using (reader)
            {
                if (reader.Read())
                {
                    int fcount = reader.FieldCount;
                    fieldNames = new string[fcount];
                    object[] values = null;

                    for (int i = 0; i < fcount; i++)
                        fieldNames[i] = reader.GetName(i);

                    do
                    {
                        values = new object[fcount];
                        reader.GetValues(values);
                        list.Add(values);
                    } while (reader.Read());

                }
            }
            this.Values = list;
            return list.Count;
        }
複製代碼

程序中使用 reader.GetValues(values) 方法,它不必對每列進行數據讀取,所以數據讀取的效率較高。

現在數據存放進去了,如何使用呢?爲了做到通用,具體每個數據的使用還是交給使用者自己去處理吧,所以採用一個委託方法來處理:

 

複製代碼
        /// <summary>
        
/// 採用自定義的映射方式,將數據容器中的數據映射到指定的類中 
        
/// </summary>
        
/// <typeparam name="TResult">結果類型</typeparam>
        
/// <param name="fun">處理數據的方法</param>
        
/// <returns></returns>
        public IEnumerable<TResult> Map<TResult>(Func<TResult> fun) where TResult : classnew()
        {
            if (this.Values != null && this.fieldNames != null)
            {
                foreach (object[] itemValues in this.Values)
                {
                    TResult t = new TResult();
                    this.currValue = itemValues;
                    fun(t);
                    yield return t;
                }
            }
            else
            {
                throw new Exception("EntityContainer 錯誤,調用該方法前請先調用Execute 方法。");
            }
        }
複製代碼

下面是該方法的使用示例:

 

複製代碼
            EntityContainer ec = new EntityContainer(q, db);
            ec.Execute();
            var mapUser2= ec.Map<User>((e) => 
            {
                e.Age = ec.GetItemValue<int>("Age");
                e.ID = ec.GetItemValue<int>("ID");
                e.Name = ec.GetItemValue<string>("name");//不區分大小寫
                return e; 
            }
            ).ToList ();
複製代碼

 除了可以使用 GetItemValue<T>(string fieldName) 方法來獲取迭代的當前行的某列數據外,也可以使用 GetItemValue<T>(int fieldIndex) 方法。

另外,還提供了一個將數據映射到PDF.NET實體類的方法,下面是方法的定義:

 

複製代碼
        /// <summary>
        
/// 將數據從容器中映射到實體中
        
/// </summary>
        
/// <typeparam name="T"></typeparam>
        
/// <returns></returns>
        public IEnumerable<T> Map<T>() where T : EntityBase{
             //具體代碼略
        }
複製代碼

上面的測試例子中,User類是一個實體類,所以可以用下面的方式直接獲取該類的實例對象集合:

 

            EntityContainer ec = new EntityContainer(q, db);
            ec.Execute();
            var mapUser1 = ec.Map<User>().ToList ();

在Map方法中,可以映射出任意PDF.NET實體類,或者其它自定義的POCO實體類,而且沒有映射次數限制。看到這裏聰明的你也許要問了,上面的例子可以映射User之外的實體嗎?答案是完全可以!

先看一個例子,我們假設系統中還存在一個實體類 Group,我們使用PDF.NET的OQL表達式寫一個支持兩個實體連接查詢的語句:

 

OQL q=OQL.From(user)
         .InnerJoin(group) //連接Group實體
         .On(user.GroupID,group.ID)
         .Select(user.ID,user.Name,group.GroupName) //選取指定的字段

下面就可以映射出兩個實體集合了:

 

            EntityContainer ec = new EntityContainer(q, db);
            ec.Execute(); //可以省略此行調用
            var mapUser1 = ec.Map<User>().ToList ();
            var mapGroup1= ec.Map<Group>().ToList();

如果覺得這樣分別使用兩個實體對象集合( user和group)比較麻煩,那麼再自定義一個“用戶機構”類即可:

 

 class UserGroup 
{
    int ID{get;set;}
    string Name{get;set;}
    string GroupName{get;set;}
}

之後,可以像下面這樣來使用數據:

 

複製代碼
            EntityContainer ec = new EntityContainer(q, db);
            //ec.Execute();//可以省略此行調用

            var mapEntity= ec.Map<UserGroup>((e) => 
            {
                e.GroupName = ec.GetItemValue<int>("GroupName");
                e.ID = ec.GetItemValue<int>("ID");
                e.Name = ec.GetItemValue<string>("name");//不區分大小寫
/*

//或者使用索引的方式,但必須明確上面OQL表達式Select方法裏面屬性的順序 
                e.GroupName = ec.GetItemValue<int>(2);
                e.ID = ec.GetItemValue<int>(0);
                e.Name = ec.GetItemValue<string>(1);

*/
                return e; 
            }
            ).ToList ();
複製代碼

  

上面的寫法沒有LINQ那麼完美,人家LINQ是近水樓臺先得月,MS自家的苗子,可以依靠“編譯器語法糖”來寫出優美的LINQ程序,但我們的這個實現從原理上說非常輕巧,在衆多非官方的ORM框架中,真正支持了實體類的多表連接查詢!

 

有關OQL的多實體連接查詢僅在PDF.NET框架V4.1以後版本支持,該功能作爲框架的一項重要功能擴展,已經在商業項目中開始使用,感興趣的朋友可以一起研究。

下面的代碼是實際項目中的一段代碼,我們來看看完整的調用方式:

 

複製代碼
public string GetTradeTypeID(string foundAccount,string jjdm,string type)
        {
            /*執行下面的查詢將使用如下類似的SQL:
             select distinct a.tradetype tradetypeid from wft_customerfundtrade a
                 left join wft_customerfundtradedetails b on a.tradetype =b.tradetypeid
                      where   (a.fundaccount ='1185919705'  and a.jjdm ='KF0003')
                         and ( b.tradetype='定投' or b.tradetype='基金轉換的記帳')
             * 
             
*/
            WFT_CustomerFundTrade trade = new WFT_CustomerFundTrade() { FundAccount = foundAccount, JJDM = jjdm };
            WFT_CustomerFundTradeDetails detail = new WFT_CustomerFundTradeDetails() { TradeType = type };
            OQLCompare cmpAll = new OQLCompare(trade, detail);

            OQL q = OQL.From(trade)
                .LeftJoin(detail).On(trade.TradeType, detail.TradeTypeId)
                .Select(trade.TradeType)
                .Where(
                        (cmpAll.Comparer(trade.FundAccount) & cmpAll.Comparer(trade.JJDM)) &
                        (cmpAll.Comparer(detail.TradeType) | cmpAll.Comparer(detail.TradeType, "=""基金轉換的記帳"))
                )
                .END;

            q.Distinct = true;

            EntityContainer container = new EntityContainer(q);
            var result = container.Map<WFT_CustomerFundTrade>().ToList();
            if (result.Count == 0)
                return "";
            else
                return result[0].TradeType;
}
複製代碼

由這個例子可以看出,PDF.NET的ORM框架中的實體對象查詢語言--OQL,已經可以完成很複雜的查詢了,包括多實體類關聯查詢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章