程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#發現之旅第十一講 使用反射和特性構造自己的ORM框架(上)

C#發現之旅第十一講 使用反射和特性構造自己的ORM框架(上)

編輯:關於C#

ORM背景

在數據庫界,主流的數據庫都是關系型數據庫,其采用的關系型數據結構模型,無論從數學上還是實踐中都相當的成熟,得到非常廣泛的應用。在關系型數據結構理論中,所有的數據都組織成一個個相互獨立的二維表格,一個數據表有若干行若干列。因此關系型數據庫適合存儲大量的結構簡單的數據,不適合存儲復雜的數據結構。

在編程界,面向對象的編程思想及其派生思想占據主流。面向對象的編程思想具有封裝,繼承,重載等手段來方便的實現比較復雜的數據結構,這適應了現代信息系統包含大量復雜數據結構的特點。因此面向對象的編程思想得到廣泛應用。

關系型數據模型和面向對象的編程思想之間存在比較大的差別,數據在兩者之間的交換是不大通暢的,就像南京,江北和主城區之間是長江來阻斷交通。因此開發人員迫切需要破解這種數據交通的阻斷。

以前程序員需要編程,從數據庫中讀取一個個字段值並賦值到編程對象的一個個字段或屬性上,這就像在長江上架一個獨木橋,開發效率低下,維護困難。後來出現一種叫ORM的框架性的程序,它能根據某些配置信息將數據庫中的字段和編程對象的字段或屬性之間建立映射關系,從而能方便的從數據庫讀取字段值並賦值到對象屬性中。這是一種半自動的機制,能比較大的提高開發效率,簡化維護,這就像在長江上架設了一座高速公路大橋。

從本質上說,關系型數據庫和面向對象的編程思想之間的隔閡非常大,就像長江是天澗,即使建五六座大橋也不夠用。徹底的解決方法就是拋棄關系型數據庫而使用面向對象的數據庫,不過這個過程就像南京江北整體搬遷到江南一樣,工程浩大,時間漫長。在等待面向對象數據庫的統治前,我們仍然得忍受關系型數據庫和面向對象編程思想之間的數據交通不暢的痛苦,並使用ORM框架來很有限的降低這種痛苦。從這個角度上說,我們痛恨關系型數據庫,就像搞運載火箭的人痛恨地球引力一樣。

反射和特性

反射是.NET框架提供的一種高級編程接口。學過VB的都知道VB中有一個CallByName函數,它能調用對象的指定名稱的成員方法,比如有個窗體對象,我們可以調用“frm.Close()”來關閉窗體,也可以調用“CallByName( frm , “Close”)”來實現同樣的功能。而反射就是CallByName的.NET版本,而且功能更加強大。使用反射,我們可以列出任何對象類型的所有的字段,屬性,方法和事件的名稱,包括公開的或私有的。我們還可以更深入的獲得字段的數據類型,成員方法的參數的個數,類型及其返回值;事件使用的委托類型等等。可以說反射技術就是.NET框架提供的只讀的程序基因分析技術。

.NET框架對反射這種程序基因分析技術提供了天然的支持。在.NET框架中,任何對象類型都是從object類型上面派生的,object類型有一個GetType函數,該函數返回一個System.Type類型的對象,該對象就是反射操作的入口點,這樣任何.NET對象類型都能用反射技術進行分析。

特性也是.NET框架提供的一種高級編程手段。它是附加在類型,字段,屬性,函數等編程單元上面的額外信息,就相當於Access數據庫中的表和字段的說明文本。它不會影響所附著的編程單元的正常執行。但它是客觀存在的,可以通過反射來獲得其信息,一般的我們可以調用System.Attribute類型的GetCustomAttribute函數來獲得指定編程單元附加的指定類型的特性對象。

從編程角度看,特性也是一種對象類型,它們都是從System.Attribute上面派生的。.NET類庫中已經定義了大量的特性類型,我們也可以定義自己的特性。

使用特性也很簡單,也就是在編程單元前面使用方括號包含特性的定義,比如對於WinForm程序其入口函數具有以下類似的定義。在這裡,函數定義前頭的“[Ssystem.STAThread]”就表示這個函數附加了一個類型名為“System.STAThreadAttribute”的特性,這裡存在一個C#的語法,在附加特性時可以將類型名稱後面的“Attribute”後綴去掉,當然也可以寫全名。一個編程單元可以附加多個特性。

/// <summary>
/// 應用程序的主入口點。
/// </summary>
[System.STAThread]
static void Main()
{
     Application.Run(new frmTestORM());
}

使用特性,我們可以在對象屬性上附加數據庫字段映射信息,使用反射,我們可以動態的讀取和設置對象的屬性值。這樣特性和反射可以成為我們實行ORM的技術基礎。

關於反射和特性的詳細信息可以參考MSND中的相關文檔。

ORM框架設計

我們首先來設計一個ORM框架。說到框架大家一定聯想到.NET框架,J2EE框架。這些框架都是大公司勞苦數年才能完成,結構復雜功能強大。其實我們都可以根據各自需要自己開發一些通用的支持性質的軟件,並美其名曰框架。現在我們就來設計一個輕量級的ORM框架,並應用我們今天要學習的反射和特性的.NET編程技術。

既然是輕量級的,我們就不考慮所有的情況,只考慮經常遇到的簡單情況,復雜情況不予考慮。很多時候我們的編程對象和數據庫表之間是存在簡單的影射關系的,比如一個對象類型對應一個數據表,對象的一個屬性對應數據表中的一個字段。此時我們可以定義兩種特性,一個數據數據庫表綁定特性,名為BindTableAttribute,用於將一個對象類型綁定到一個指定表名稱的數據表上;還有一個數據庫字段綁定特性,名為BindFieldAttribute,用於將一個對象屬性綁定到一個指定名稱的字段上面。

下圖就是一個映射關系的例子,數據庫中有個名為Employees的數據表,而開發者定義了DB_Employees類型。通過使用BindTableAttribute特性,將DB_Employess類型映射到數據表Employees,而是用BindFieldAttribute特性將DB_Employees的EmployeeID屬性映射到數據庫字段EmployeeID上面。類似的DB_Employees中的很多屬性都映射到數據表Employees中的某個字段上,當然不是所有的對象類型的屬性映射到數據庫字段。通過在程序代碼中,我們可以使用硬編碼的方式將對象類型及其屬性映射到數據庫中的表和字段上面。

這種將映射信息保存在代碼中的方式有利有弊,好處是程序代碼比較集中,修改代碼方便,壞處就是當數據庫結構或者映射關系發生改變時,需要修改代碼,這導致重新編譯重新部署。一些ORM框架使用XML配置文件來保存對象和數據庫的映射關系,不過這會導致代碼,數據庫和映射配置文件的三者同步更新的操作,工作量大,會加大開發成本,當然好處是當數據庫結構或者映射關系發生改變時,只需要修改數據庫和配置文件,程序代碼不需要更新,從這方面看有利於系統的維護。不過在很多實踐中,數據庫或映射關系改變時,很容易導致程序代碼必須作相應的修改,此時會導致代碼,數據庫和映射配置文件的同步更新工作。因此映射配置信息采用何種保存模式需要開發者自己權衡,不過在這裡由於是要演示使用反射和特性的,因此映射配置信息是保存在代碼中的。當然我們可以建立一個ORM框架,既支持使用特性存儲映射關系,也可以使用映射配置文件,不過比較復雜,本框架程序是演示程序,不會實現該功能。

開發者在編制存儲數據的類型後,使用BindTableAttribute和FieldBindAttribute特性建立了映射關系後,ORM框架程序就能根據這些類型來操作數據庫了,目前的設計將提供以下幾種功能。

查詢數據庫,返回對象

使用本功能,框架可以根據指定的SQL查詢語句和對象類型查詢數據庫,並根據查詢結果生成若干個對象,並設置剛剛創建的對象的屬性值為字段值。在這個功能中,需要首先指定SQL查詢語句和對象類型。

在這個功能中,框架程序首先獲得對象類型的所有公開屬性,獲得其附加的BindFieldAttribute特性,獲得這些屬性綁定的數據庫字段名。然後執行SQL查詢,遍歷所有查詢的紀錄,對每一個記錄都創建一個數據對象,並根據字段和屬性的映射關系將數據庫字段值保存到對象的屬性值中。如此就實現了查詢數據庫獲得對象的功能。

這個功能中需要用戶指定SQL查詢語言,也可以根據對象類型綁定的數據表名稱來自己拼湊SQL語句。

將對象插入到數據庫

在本功能中,框架程序使用反射獲得對象類型附加的BindTableAttribute特性,獲得該對象映射的數據表名;然後遍歷所有的公開實例屬性,若屬性附加了BindFieldAttribute特性,則獲得該屬性映射的字段名。然後收集所有的屬性值和它們映射的字段名,使用字符串拼湊生成一個Insert的SQL語句。然後調用數據庫連接對象執行這個SQL語句,以實現向數據庫新增記錄的功能。

根據對象修改數據庫記錄

在本功能中,框架程序使用指定的對象來修改數據庫中的記錄。此時對象類型中至少有一個屬性附加了關鍵字段映射特性。框架程序使用反射獲得對象類型附加的BindTableAttribute 特性,獲得該對象映射的數據表名,然後遍歷屬性,獲得對象屬性和數據庫字段之間的映射關系。然後收集屬性值,使用字符串拼湊生成一個“Update 數據表名 Set 字段1=屬性1的值 ,字段2=屬性2的值 ”的SQL語句。然後還遍歷屬性,找到所有附加了關鍵字段特性的屬性以及綁定的字段名,拼湊出“Where 關鍵字段1=屬性1的值 and 關鍵字段2=屬性2的值”,這樣就能拼湊出一個完整的更新數據庫用的Update的SQL語句,然後調用數據庫連接對象執行這個SQL語句,就能實現更新數據庫記錄的功能。

根據對象刪除數據庫記錄

在本功能中,框架程序獲得對象類型綁定的數據表名,並遍歷所有的附加了綁定關鍵字段的特性,然後拼湊出“Delete From 數據表名Where 關鍵字段1=屬性1的值 and 關鍵字段2=屬性2的值”的SQL語句,然後調用數據庫連接對象來執行這個SQL更新語句,這樣就實現了刪除數據庫記錄的功能。

框架程序代碼說明

根據程序設計,我已經初步的把框架程序開發出來,現在對其源代碼進行說明。

數據表綁定信息 BindTableAttribute類型

框架程序中首先定義了BindTableAttribute類型,該類型就保存了對象類型映射的數據庫表的名稱。其源代碼為

/// <summary>
/// 數據表名綁定特性
/// </summary>
[System.AttributeUsage( System.AttributeTargets.Class , AllowMultiple = false ) ]
public class BindTableAttribute : System.Attribute
{
    /// <summary>
    /// 初始化對象
    /// </summary>
    public BindTableAttribute( )
    {
    }
    /// <summary>
    /// 初始化對象
    /// </summary>
    /// <param name="name">數據表名</param>
    public BindTableAttribute( string name )
    {
        strName = name ;
    }
    private string strName = null;
    /// <summary>
    /// 數據表名
    /// </summary>
    public string Name
    {
        get
        {
            return strName ;
        }
    }
}

BindTableAttribute演示了如何實現自己的特性。所有的特性都是從System.Attribute類型上面派生的,在定義類型時還使用System.AttributeUsage特性來表明這個自定義特性的使用范圍,這裡使用了Class樣式,表示BindTableAttribute特性只能用在其它的Class類型前面,若放置在Interface或Struct類型前面,或者放在對象成員的前面則會出現編譯錯誤。這裡還是用語句 AllowMultiple=false 語句來表明對於一個類型,該特性只能用一次,若一個Class類型前面出現多個BindTableAttriubte,則會出現編譯錯誤。若設置AllowMultiple=true,則該特性可以多次定義,也就是一個Class類型前面可以出現多個相同類型的特性。不過這裡我們假設一個對象只能映射到一個數據表上,沒有多重映射,因此就指明對同一個類型該特性不能多次使用。

特性也是一個Class類型,可以有多個構造函數,就像C#的new語句一樣,我們向類型附加特性時可以使用不同的初始化參數來指明使用特性的那個構造函數。我們附加特性時還可以使用“屬性名=屬性值”的方法來直接指明特性的屬性值。這有點類似VB中調用函數時使用“參數名=參數值”的語法。

該特性中定義了一個Name屬性,該屬性就是被修飾的對象所映射的數據庫表的名稱。框架程序就讀取BindTableAttribute特性的Name值來作為數據對象映射的數據表名,若Name值為空則認為對象類型的名稱就是映射的數據表的名稱。

若對象沒有附加BindTableAttribute特性,則該對象沒有映射到任何數據表上,因此不能讓框架程序使用它來操作數據庫。

數據字段綁定信息 BindFieldAttribute類型

框架程序中定義了BindFieldAttribute類型,該類型就保存了對象的屬性映射的數據庫字段的名稱,轉換格式和關鍵字段樣式,其源代碼為

[System.AttributeUsage( System.AttributeTargets.Property , AllowMultiple = false ) ]
public class BindFieldAttribute : System.Attribute
{
    /// <summary>
    /// 初始化對象
    /// </summary>
    public BindFieldAttribute( )
    {
    }
    /// <summary>
    /// 初始化對象
    /// </summary>
    /// <param name="name">字段名</param>
    public BindFieldAttribute( string name )
    {
        strName = name ;
    }
    private string strName = null;
    /// <summary>
    /// 數據字段名
    /// </summary>
    public string Name
    {
        get
        {
            return strName ;
        }
    }
    private bool bolKey = false;
    /// <summary>
    /// 該字段為關鍵字段,可用作查詢條件
    /// </summary>
    public bool Key
    {
        get
        {
            return bolKey ;
        }
        set
        {
            bolKey = value;
        }
    }
    private string strReadFormat = null;
    /// <summary>
    /// 數據讀取格式化字符串
    /// </summary>
    public string ReadFormat
    {
        get
        {
            return strReadFormat ;
        }
        set
        {
            strReadFormat = value ;
        }
    }
    private string strWriteFormat = null;
    /// <summary>
    /// 數據存儲格式化字符串
    /// </summary>
    public string WriteFormat
    {
        get
        {
            return strWriteFormat ;
        }
        set
        {
            strWriteFormat = value;
        }
    }
}//public class BindFieldAttribute : System.Attribute

在BindFieldAttribute中,首先我們使用AttributeUsage特性來描述了這個特性的應用范圍,這裡使用了System.AttributeTargets.Property來表明該特性只能用於對象類型的屬性而不能用於其它任何地方。

這裡定義了Name屬性,就是其所依附的數據對象的屬性映射的數據庫字段的名稱,若Name值為空則認為屬性名就是映射的數據庫字段名。若數據對象的屬性沒有附加BindFieldAttribute特性,則該屬下沒有映射到任何數據庫字段上,框架程序會忽略這個成員屬性的存在。

這裡還定義了Key屬性,用於表明所映射的字段是不是關鍵字段。框架程序在修改和刪除數據庫記錄時需要獲得查詢條件,而對象類型中所有的附加了BindFieldAttribute特性且Key值為true的屬性就可構造出查詢條件,若對象類型中沒有任何一個Key值為true的成員屬性,則框架程序不能根據其來修改和刪除數據庫記錄。

ReadFormat屬性用於指明從數據庫讀取的原始數據設置到對象屬性值時的解析格式。比如數據庫中保存了類似“20080603”的格式為“yyyyMMdd”的日期數據,而對象屬性的數據類型是DateTime類型。此時我們可以設置ReadFormat值為“yyyyMMdd”則框架程序從數據庫獲得原始數據後試圖用“yyyyMMdd”的格式解析原始數據並獲得一個DateTime值,然後設置到對象屬性值。

WriteFormat屬性類似ReadFormat屬性,用於指明將數據對象的屬性值按照指定的格式化生成一個字符串並保存到數據庫中。

主框架模塊 MyORMFramework類型

類型 MyORMFramework是本ORM框架的主要程序模塊,它根據類型BindTableAttribute和BindFieldAttribute提供的信息將應用程序對象和數據庫的表和字段進行綁定,然後向數據庫查詢,新增,修改和刪除數據庫記錄。應用程序使用ORM框架也基本上就是創建一個MyORMFramework的實例,然後調用它的成員。

獲得對象-數據庫綁定信息

框架要實現ORM框架功能,第一步就是得獲得應用程序對象和數據庫的映射關系,在MyORMFramework類型中定義了GetBindInfo函數來獲得這種關系。該函數的參數是應用程序的對象類型,返回值是TableBindInfo類型,該類型就是對象-數據庫映射信息。由於BindTableAttribute和BindFieldAttribute類型適合對應用對象類型做標記,但不適合快速查詢信息,因此這裡額外定義了TableBindInfo和FieldBindInfo類型保存映射關系,這兩個類型的代碼為

/// <summary>
/// 數據表綁定信息對象
/// </summary>
private class TableBindInfo
{
     /// <summary>
     /// 數據庫表名
     /// </summary>
     public string TableName = null;
     /// <summary>
     /// 對象類型
     /// </summary>
     public Type ObjectType = null;
     /// <summary>
     /// 綁定信息對象
     /// </summary>
     public BindTableAttribute Attribute = null;
     /// <summary>
     /// 綁定的字段信息對象
     /// </summary>
     public FieldBindInfo[] Fields = null;
     /// <summary>
     /// 綁定的字段列表,格式為"字段1,字段2,字段3"
     /// </summary>
     public string FieldNameList = null;
}
/// <summary>
/// 數據字段綁定信息對象
/// </summary>
private class FieldBindInfo
{
     /// <summary>
     /// 綁定的字段名
     /// </summary>
     public string FieldName = null;
     /// <summary>
     /// 綁定的字段序號
     /// </summary>
     public int FieldIndex = - 1;
     /// <summary>
     /// 對象屬性信息
     /// </summary>
     public System.Reflection.PropertyInfo Property = null;
     /// <summary>
     /// 數據類型
     /// </summary>
     public Type ValueType = null;
     /// <summary>
     /// 默認值
     /// </summary>
     public object DefaultValue = null;
     /// <summary>
     /// 綁定信息對象
     /// </summary>
     public BindFieldAttribute Attribute = null;
     /// <summary>
     /// 將對象數據轉換為數據庫中的數據
     /// </summary>
     /// <param name="v">對象數據</param>
     /// <returns>數據庫數據</returns>
     public object ToDataBase( object v )
     {
         if( v == null || DBNull.Value.Equals( v ))
              return DBNull.Value ;
         string Format = Attribute.WriteFormat ;
         if( Format != null && Format.Trim().Length > 0 )
         {
              if( v is System.IFormattable )
              {
                   v = ( ( System.IFormattable ) v ).ToString( Format , null );
              }
         }
         return v ;
     }
     /// <summary>
     /// 將從數據庫中獲得的數據轉換為對象數據
     /// </summary>
     /// <param name="v">從數據庫獲得的原始數據</param>
     /// <returns>轉化後的對象數據</returns>
     public object FromDataBase( object v )
     {
         // 若數據為空則返回默認值
         if( v == null || DBNull.Value.Equals( v ))
              return DefaultValue ;
         // 進行格式化解析
         string Format = Attribute.ReadFormat ;
         if( Format != null && Format.Trim().Length > 0 )
         {
              string Value = Convert.ToString( v );
              if( ValueType.Equals( typeof( DateTime )))
              {
                   if( Format == null )
                       return DateTime.Parse( Value );
                   else
                       return DateTime.ParseExact( Value , Format , null );
              }
              else if( ValueType.Equals( typeof(byte )))
              {
                   return byte.Parse( Value );
              }
              else if( ValueType.Equals( typeof( short )))
              {
                   return short.Parse( Value );
              }
              else if( ValueType.Equals( typeof( int )))
              {
                   return int.Parse( Value );
              }
              else if( ValueType.Equals( typeof( float )))
              {
                   return float.Parse( Value );
              }
              else if( ValueType.Equals( typeof( double )))
              {
                   return double.Parse( Value );
              }
              return Convert.ChangeType( Value , ValueType );
         }
         if( v.GetType().Equals( ValueType ) || v.GetType().IsSubclassOf( ValueType ))
         {
              // 若數據類型匹配則直接返回數值
              return v ;
         }
         else
         {
              // 若讀取的值和對象數據的類型不匹配則進行數據類型轉換
              System.ComponentModel.TypeConverter converter =
                   System.ComponentModel.TypeDescriptor.GetConverter( ValueType );
              if( converter != null && converter.CanConvertFrom( v.GetType()) )
              {
                   return converter.ConvertFrom( v ) ;
              }
              return Convert.ChangeType( v , ValueType );
         }
     }//public object FromDataBase( object v )
}

類型TableBindInfo用於保存對象類型和數據庫表的映射關系,類型FildBindInfo用於保存對象屬性和數據庫字段的映射關系。TableBindInfo定義了一個Fields字段,是FieldBindInfo類型的數組,用於保存所有的對象屬性和數據庫字段的映射關系。這樣我們就用TableBindInfo和FieldBindInfo組成了一個兩層的樹狀列表,方便我們查詢對象和數據庫的綁定關系。

函數GetBindInfo的代碼為

/// <summary>
/// 在內部緩存的映射信息列表,此處為了提高速度。
/// </summary>
private static System.Collections.Hashtable myBufferedInfos = new System.Collections.Hashtable();
/// <summary>
/// 獲得指定類型的數據表映射信息對象
/// </summary>
/// <param name="ObjectType">對象類型</param>
/// <returns>獲得的映射信息對象</returns>
/// <remarks>
/// 本函數內部使用了 myBufferedInfos 來緩存信息,提高性能。
/// </remarks>
private TableBindInfo GetBindInfo( Type ObjectType )
{
     if( ObjectType == null )
     {
         throw new ArgumentNullException("OjbectType");
     }
     // 查找已緩存的映射信息對象
     TableBindInfo info = ( TableBindInfo ) myBufferedInfos[ ObjectType ] ;
     if( info != null )
     {
         return info ;
     }
     // 若未找到則創建新的映射信息對象
     BindTableAttribute ta = ( BindTableAttribute ) System.Attribute.GetCustomAttribute( 
         ObjectType , typeof( BindTableAttribute ));
     if( ta == null )
     {
         return null;
     }
     TableBindInfo NewInfo = new TableBindInfo();
     NewInfo.ObjectType = ObjectType ;
     NewInfo.Attribute = ta ;
     NewInfo.TableName = ta.Name ;
     if( NewInfo.TableName == null || NewInfo.TableName.Trim().Length == 0 )
     {
         // 若在特性中沒有指明綁定的表名則使用默認的對象類型名稱
         NewInfo.TableName = ObjectType.Name ;
     }
     System.Text.StringBuilder myFieldList = new System.Text.StringBuilder();
     System.Collections.ArrayList fields = new System.Collections.ArrayList();
     // 遍歷所有的公開的實例屬性來獲得字段綁定信息
     foreach( System.Reflection.PropertyInfo p in ObjectType.GetProperties(
         System.Reflection.BindingFlags.Instance 
         | System.Reflection.BindingFlags.Public
         ))
     {
         BindFieldAttribute fa = ( BindFieldAttribute ) Attribute.GetCustomAttribute(
              p , typeof( BindFieldAttribute ));
         if( fa != null )
         {
              FieldBindInfo NewFieldInfo = new FieldBindInfo();
              NewFieldInfo.Attribute = fa ;
              NewFieldInfo.FieldName = fa.Name ;
              if( NewFieldInfo.FieldName == null || NewFieldInfo.FieldName.Trim().Length == 0 )
              {
                   // 若在特性中沒有指明綁定的字段名則使用默認的屬性名稱
                   NewFieldInfo.FieldName = p.Name ;
              }
              if( myFieldList.Length > 0 )
              {
                   myFieldList.Append(",");
              }
              myFieldList.Append( FixFieldName( NewFieldInfo.FieldName ) ) ;
              NewFieldInfo.Property = p ;
              NewFieldInfo.ValueType = p.PropertyType ;
              NewFieldInfo.DefaultValue = GetDefaultValue( p );
              fields.Add( NewFieldInfo );
         }
     }
     NewInfo.Fields = ( FieldBindInfo[]) fields.ToArray( typeof( FieldBindInfo ));
     NewInfo.FieldNameList = myFieldList.ToString();
     // 緩存綁定信息對象
     myBufferedInfos[ ObjectType ] = NewInfo ;
     return NewInfo ; 
  }

為了提高程序性能,本類型內部定義了一個myBufferedInfos的哈希列表,該列表保存了各種應用程序類型和數據庫的映射關系,在GetBindInfo函數中,程序首先檢查myBufferedInfos列表獲得緩存的數據。若緩存中沒有所需的數據,則使用Attribute.GetCustomAttribute函數從指定的對象類型中獲得附加的BindTableAttribute特性,若沒有獲得特性則表示對象類型沒有附加特性,因此無法獲得任何綁定信息。若獲得了一個BindTableAttribute對象,則創建一個TableBindInfo對象,填充TableBindInfo中的信息。

接著程序調用對象類型的GetProperties函數獲得該對象類型中定義的所有屬性,這裡使用了一種System.Reflection.BindingFlags類型的參數,該參數就指明獲得的屬性的樣式,其中Instance樣式指明只查找實例屬性,而不是靜態屬性;Public樣式指明只查找公開的屬性(public),不查找私有的屬性。GetProperties函數返回的是一個System.Reflection.PropertyInfo類型的數組,然後我們遍歷所有找到的屬性,對每一個屬性調用Attribute.GetCustomAttribute函數獲得該屬性附件的類型為BindFieldAttribute特性,若找到則表示該對象屬性映射到數據庫字段中,於是創建一個FieldBindInfo類型的對象,填充該對象。完成對所有的屬性的遍歷後,我們就設置TableBindInfo的Fields字段,然後將獲得的TableBindInfo對象保存在myBufferedInfos中,最後函數返回創建的TableBindInfo對象,這個就是指定應用對象類型的數據庫映射信息。

在ORM框架操作數據庫時會頻繁的調用GetBindInfo函數獲得對象類型的數據庫映射關系,因此需要使用myBufferedInfos哈希列表緩存這種映射關系,這會比較大的提高框架程序的運行性能。

查詢數據

框架定義了一個ReadObjects的函數用於查詢數據庫獲得數據,並根據對象-數據庫映射關系創建若干個應用程序對象。其主要代碼為

/// <summary>
/// 使用指定的SQL查詢語句查詢數據庫並讀取多條數據庫記錄對象
/// </summary>
/// <param name="strSQL">SQL查詢語句</param>
/// <param name="ObjectType">要讀取的對象類型</param>
/// <param name="MaxObjectCount">最多讀取的對象個數</param>
/// <returns>讀取的對象組成的數組</returns>
public object[] ReadObjects( string strSQL , Type ObjectType , int MaxObjectCount )
{
     // 檢查參數
     if( strSQL == null )
     {
         throw new ArgumentNullException("strSQL");
     }
     if( ObjectType == null )
     {
         throw new ArgumentNullException("ObjectType");
     }
     // 檢查數據庫映射信息
     this.CheckBindInfo( ObjectType , false );
     // 檢查數據庫連接
     this.CheckConnetion();
     // 創建SQL命令對象
     using( System.Data.IDbCommand cmd = myConnection.CreateCommand())
     {
         // 執行SQL查詢,獲得一個數據讀取器
         cmd.CommandText = strSQL ;
         System.Data.IDataReader reader = cmd.ExecuteReader(
              MaxObjectCount == 1 ?
              System.Data.CommandBehavior.SingleRow :
              System.Data.CommandBehavior.SingleResult );
         System.Collections.ArrayList list = new System.Collections.ArrayList();
         TableBindInfo table = this.GetBindInfo( ObjectType );
         lock( table )
         {
              // 設置字段序號,提高性能
              foreach( FieldBindInfo field in table.Fields )
              {
                   field.FieldIndex = - 1 ;
              }
              for( int iCount = 0 ; iCount < reader.FieldCount ; iCount ++ )
              {
                   string name = reader.GetName( iCount );
                   foreach( FieldBindInfo field in table.Fields )
                   {
                       if( EqualsFieldName( name , field.FieldName ))
                       {
                            field.FieldIndex = iCount ;
                       }
                   }
              }
              while( reader.Read())
              {
                   // 根據對象類型創建對象實例
                   object obj = System.Activator.CreateInstance( ObjectType );
                   // 讀取對象屬性值
                   if( InnerReadValues( obj , table , reader ) > 0 )
                   {
                       list.Add( obj );
                   }
                   if( MaxObjectCount > 0 || list.Count == MaxObjectCount )
                   {
                       break;
                   }
              }//while
         }//lock
         reader.Close();
         // 返回讀取的對象數組
         return list.ToArray();
     }//using
}
/// <summary>
/// 從數據讀取器中讀取數據並填充到一個對象中
/// </summary>
/// <param name="ObjInstance">對象實例</param>
/// <param name="info">數據庫綁定信息對象</param>
/// <param name="reader">數據讀取器</param>
private int InnerReadValues( object ObjInstance , TableBindInfo info , System.Data.IDataReader reader )
{
     // 檢查參數
     if( ObjInstance == null )
     {
         throw new ArgumentNullException("ObjectInstance");
     }
     if( info == null )
     {
         throw new ArgumentNullException("info");
     }
     if( reader == null )
     {
         throw new ArgumentNullException("reader");
     }
     int FieldCount = 0 ;
     // 依次讀取各個屬性值
     foreach( FieldBindInfo field in info.Fields )
     {
         // 綁定的屬性是只讀的
         if( field.Property.CanWrite == false )
         {
              continue ;
         }
         // 沒有找到綁定的字段
         if( field.FieldIndex < 0 )
         {
              continue ;
         }
         // 讀取數據字段值
         object v = reader.GetValue( field.FieldIndex );
         v = field.FromDataBase( v );
         // 設置對象屬性值
         field.Property.SetValue( ObjInstance , v , null );
         FieldCount ++ ;
     }//foreach
     return FieldCount ;
}

該函數的參數為SQL查詢語句,要創建的對象類型和最多能創建的對象的個數。在該函數中,首先根據數據庫連接對象創建一個命令對象,然後執行SQL查詢語句,獲得一個System.Data.IDataReader對象,然後調用GetBindInfo函數獲得對象類型和數據庫的映射信息。首先對數據讀取器的各個字段名稱和對象類型中綁定的各個屬性的名稱進行比較,設置各個對象類型的屬性對應的字段序號,以加快數據讀取速度。

然後遍歷數據讀取器,對每行記錄使用System.Activator.CreateInstance創建指定類型的對象實例,然後調用InnerReadValue函數從數據讀取器中讀取數據並填充對象的屬性值。

本文配套源碼

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