動態編譯技術
所謂動態編譯技術就是應用程序在運行時,程序內部自動的生成C# 代碼,然後調用.NET框架提供的C#程序編譯器生成臨時的程序集,然後將臨時程序集加載到 應用程序域中動態的調用其中的對象模塊。
動態編譯技術內部調用了代碼生成器。以 前我們是在編程時使用代碼生成器生成代碼文檔,然後添加到C#工程中,然後進行整體編譯 ,此時我們是手工的使用代碼生成器,這個過程可以稱為靜態編譯。而動態編譯技術卻是將 這個過程自動化了,而且調用代碼生成器生成代碼文本的過程放置在軟件運行時執行。
動態編譯技術能同時兼顧靈活性和性能。微軟.NET框架本身也有動態編譯技術的應用,比 如XML序列化和反序列化,ASP.NET框架處理ASPX文件等等。
一般而言使用動態編譯技術的過程可以為
1.應用程序需要調用動態編譯功能,則 收集一些參數,然後調用動態編譯模塊。
2.動態編譯模塊內部有一個全局的臨時編譯 的程序集的緩存列表,若根據應用程序傳遞的參數可以在緩存列表中找到相匹配的臨時程序 集則直接返回這個程序集對象。
3.動態編譯模塊收集參數,然後調用內置的代碼生成 器生成代碼字符串。
4.動態編譯模塊調用微軟.NET框架提供的C#代碼編譯器,生成一 個臨時的程序集對象。具體就是調用Microsoft.CSharp.CSharpCodeProvider 提供的方法。 在這個過程中,程序將會在磁盤上生成若干臨時文件,這個過程會受到微軟.NET框架的安全 設置的影響。
5.將臨時編譯生成的程序集對象保存到全局的臨時程序集的緩存列表, 然後向應用程序返回這個臨時程序集,而應用程序將會使用反射的手段來調用臨時程序集提 供的功能。
動態編譯 技術中生成的臨時程序集和我們使用開發工具生成的程序集沒有差別,運行速度是一樣的快 。因此動態編譯技術除了能實現靈活的功能外還提供良好的性能。
我們要使用動態編 譯技術,首先得看要實現的功能是否靈活多變,若我們要實現的功能比較簡單,使用靜態編 譯技術就足夠了,那我們就用不著使用動態編譯技術。若功能非常復雜,無法使用代碼生成 器生成代碼來實現它,則也不能使用動態編譯技術。
注意,動態編譯技術會在磁盤中 生成臨時文件,因此.NET框架的安全設置會影響到動態編譯技術的正常運行,而且使用該技 術的程序會生成C#代碼並保存到臨時文件,然後調用.NET框架的C#代碼編譯器生成臨時程序 集,而惡意軟件會在這兩個步驟間隙迅速的修改C#代碼文件並插入惡意代碼,對此動態編譯 技術無法判別。
快速ORM框架整體設計
在這裡我們將以上節課的ORM框架為基 礎,對它進行改造,加入動態編譯技術來打造一個快速ORM框架。首先我們還得使用 BindTableAttribute和BindFieldAttribute特性來描述實體類型和數據庫的綁定信息。於是 我們上節課使用的演示用的實體類型DB_Employees就原封不動的用到現在。該實體類型的代 碼為
[System.Serializable()]
[BindTable("Employees")]
public class DB_Employees
{
/// <summary>
/// 人員全名
/// </summary>
public string FullName
{
get
{
return this.LastName + this.FirstName ;
}
}
#region 定義數據庫字段變量及屬性 //////////////////////////////////////////
///<summary>
/// 字段值 EmployeeID
///</summary>
private System.Int32 m_EmployeeID = 0 ;
///<summary>
/// 字段值 EmployeeID
///</summary>
[BindField("EmployeeID" , Key = true )]
public System.Int32 EmployeeID
{
get
{
return m_EmployeeID ;
}
set
{
m_EmployeeID = value;
}
}
///<summary>
/// 字段值 LastName
///</summary>
private System.String m_LastName = null ;
///<summary>
/// 字段值 LastName
///</summary>
[BindField("LastName")]
public System.String LastName
{
get
{
return m_LastName ;
}
set
{
m_LastName = value;
}
}
其他 字段……………..
#endregion
}// 數據庫操作類 DB_Employees 定義結束
我們設計快速ORM框架的程序結構如 圖所示
框架中包含了一個實體類型注冊列表,列表中 包含了實體類型和相應的RecordORMHelper對象。應用程序在使用框架前必須注冊實體類型, 向實體類型注冊列表添加將要操作的實體類型,應用程序注冊實體列表時不會立即導致代碼 的自動生成和編譯。
我們首先定義了一個基礎的抽象類型RecordORMHelper,該類型 定義了處理實體類型和數據庫的映射操作,主要包括從一個System.Data.IDataReader讀取數 據並創建實體類型,為新增,修改和刪除數據庫記錄而初始化System.Data.IDbCommand對象 等等。該類型是快速ORM框架的核心處理對象,數據庫處理模塊將使用RecordORMHelper來作 為統一的接口來處理實體類和數據庫的映射操作。
代碼生成器分析實體類型列表中所 有沒有處理的實體類型,獲得其中的使用BindTableAttribute和BindFieldAttribute特性保 存的對象和數據庫的映射關系,針對每一個實體類型創建一個Class的代碼,該Class是從 RecordORMHelper上派生的,並實現了RecordORMHelper預留的接口。代碼生成器可以同時為 多個實體類型創建C#源代碼,此時一份C#源代碼中包含了多個從RecordORMHelper派生的 Class類。
C#代碼編譯器接受代碼生成器生成的代碼,進行編譯生成一個臨時程序集 ,該程序集中就包含了多個派生自RecordORMHelper的類型,每一個類型都專門處理某種實體 類型。編譯器在編譯程序是需要指定所引用的其他程序集,這裡包括 mscorlib.dll, System.dll和System.Data.dll,此外還包括類型RecordORMHelper所在的程序集,也就是包 括快速ORM框架的程序集,這裡的程序集不一定是DLL格式,也可能是EXE格式。於是我們編譯 程序時引用了一個EXE,這種操作在使用VS.NET等開發工具時是禁止的。從這裡可以看出,一 些使用VS.NET開發工具所不可能實現的功能我們可以編程使用.NET框架來實現。
.NET 框架自己包含了一個C#代碼編譯器,它的文件名是CSC.EXE,在.NET框架的安裝目錄下,在筆 者的電腦中其路徑是 C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\csc.exe 或者 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe ,它是一個基於命令行的編輯 器,能將C#代碼編譯生成EXE或者DLL文件。關於C#代碼編譯器可參考MSDN中的相關說明。
快速ORM框架的控制模塊接受應用程序的請求,首先檢查實體類型注冊列表,若列表 中沒有找到相應的RecordORMHelper對象,則調用代碼生成器生成代碼,然後調用C#代碼編譯 器編譯生成臨時的程序集,然後加載臨時程序集,使用反射(調用 System.Reflection.Assembly.GetType函數)找到其中所有的的RecordORMHelper類型,然後 根據類型動態的創建對象實例,並填充到實體類型注冊列表。最後調用RecordORMHelper預定 的接口來實現ORM功能。
若我們在使用快速ORM框架前,將所有可能要用到的實體對象 類型添加到實體類型注冊列表中,則快速ORM框架會生成一個臨時程序集,但我們是陸陸續續 的往ORM框架注冊實體對象類型,則快速ORM框架內部可能會多次調用代碼生成器和代碼編譯 器來生成臨時程序集,這樣最後會生成多個臨時程序集。一般的建議在使用框架前將向ORM框 架注冊所有可能用到的實體對象類型,這樣框架只會執行一次動態編譯的操作。
基礎 類型RecordORMHelper
本類型屬於ORM框架的底層模塊。其代碼為
public abstract class RecordORMHelper
{
/// <summary>
/// 對象操作的數據表名稱
/// </summary>
public abstract string TableName
{
get ;
}
/// <summary>
/// 從數據讀取器讀取數據創建一個記錄對象
/// </summary>
/// <param name="reader">數 據讀取器</param>
/// <returns>讀取的數據 </returns>
public object ReadRecord( System.Data.IDataReader reader )
{
int[] indexs = GetFieldIndexs( reader );
return InnerReadRecord( reader ,indexs );
}
/// <summary>
/// 從數據讀取器讀取數據創建若干個記錄對象
/// </summary>
/// <param name="reader">數據讀取器</param>
/// <param name="MaxRecordCount">允許讀取的最大的記錄個數,為0則無限制</param>
/// <returns>讀取的數據對象列表</returns>
public System.Collections.ArrayList ReadRecords( System.Data.IDataReader reader , int MaxRecordCount )
{
System.Collections.ArrayList list = new System.Collections.ArrayList();
int[] indexs = GetFieldIndexs( reader );
while( reader.Read())
{
object record = InnerReadRecord( reader , indexs );
list.Add( record );
if( MaxRecordCount > 0 && list.Count >= MaxRecordCount )
{
break;
}
}//while
return list ;
}
/// <summary>
/// 從一個數據讀取器中讀取一條記錄對象,必須重載
/// </summary>
/// <param name="reader">數據讀取器</param>
/// <param name="FieldIndexs">字段序號列表</param>
/// <returns>讀取的記錄對象</returns>
protected abstract object InnerReadRecord( System.Data.IDataReader reader , int[] FieldIndexs );
/// <summary>
/// 為刪除記錄而初始化數據庫命令對象
/// </summary>
/// <param name="cmd">數據庫命令對象</param>
/// <param name="objRecord">實體對象實例</param>
/// <returns>添加的SQL參數個數</returns>
public abstract int FillDeleteCommand( System.Data.IDbCommand cmd , object objRecord );
/// <summary>
/// 為插 入記錄而初始化數據庫命令對象
/// </summary>
/// <param name="cmd">數據庫命令對象</param>
/// <param name="objRecord">實體對象實例</param>
/// <returns>添加的SQL參數個數</returns>
public abstract int FillInsertCommand( System.Data.IDbCommand cmd , object objRecord );
/// <summary>
/// 為更新數據庫記 錄而初始化數據庫命令對象
/// </summary>
/// <param name="cmd">數據庫命令對象</param>
/// <param name="objRecord">實體對象實例</param>
/// <returns>添加的SQL參數個數</returns>
public abstract int FillUpdateCommand( System.Data.IDbCommand cmd , object objRecord );
/// <summary>
/// 字段列表數組
/// </summary>
protected abstract string [] RecordFieldNames
{
get ;
}
/// <summary>
/// 針對特定的數據 讀取器獲得實體對象的各個屬性對應的數據欄目的編號
/// </summary>
/// <param name="reader">數據讀取器 </param>
/// <returns>編號列表</returns>
private int[] GetFieldIndexs( System.Data.IDataReader reader )
{
if( reader == null )
{
throw new ArgumentNullException ("reader");
}
string[] FieldNames = this.RecordFieldNames ;
int[] indexs = new int[ FieldNames.Length ] ;
int FieldCount = reader.FieldCount ;
string[] names = new string[ FieldCount ] ;
for( int iCount = 0 ; iCount < FieldCount ; iCount ++ )
{
names[ iCount ] = reader.GetName( iCount ) ;
}
for( int iCount = 0 ; iCount < indexs.Length ; iCount ++ )
{
indexs[ iCount ] = -1 ;
string name = FieldNames[ iCount ] ;
for( int iCount2 = 0 ; iCount2 < FieldCount ; iCount2 ++ )
{
if( EqualsFieldName( name , names[ iCount2 ] ))
{
indexs[ iCount ] = iCount2 ;
break;
}
}
}
for( int iCount = 0 ; iCount < FieldCount ; iCount ++ )
{
string name = reader.GetName( iCount );
for( int iCount2 = 0 ; iCount2 < indexs.Length ; iCount2 ++ )
{
if( EqualsFieldName( name , FieldNames[ iCount2 ] ))
{
indexs[ iCount2 ] = iCount ;
}
}
}
return indexs ;
}
/// <summary>
/// 連接多個字符串,各個字符串之間用逗號分隔,本函 數會在動態生成的派生類中使用
/// </summary>
/// <param name="strs">字符串集合</param>
/// <returns>連接所得的大字符串</returns>
protected string ConcatStrings( System.Collections.IEnumerable strs )
{
System.Text.StringBuilder myStr = new System.Text.StringBuilder();
foreach( string str in strs )
{
if( myStr.Length > 0 )
{
myStr.Append(",");
}
myStr.Append( str );
}//foreach
return myStr.ToString();
}
/// <summary>
/// 判斷兩個字段名是否等價
/// </summary>
/// <param name="name1">字 段名1</param>
/// <param name="name2">字段名 2</param>
/// <returns>true:兩個字段名等價 false:字 段名不相同</returns>
private bool EqualsFieldName( string name1 , string name2 )
{
if( name1 == null || name2 == null )
{
throw new ArgumentNullException("name1 or name2");
}
name1 = name1.Trim();
name2 = name2.Trim();
// 進行不區分大小寫的比較
if( string.Compare( name1 , name2 , true ) == 0 )
{
return true ;
}
int index = name1.IndexOf(".");
if( index > 0 )
{
name1 = name1.Substring( index + 1 ).Trim();
}
index = name2.IndexOf(".");
if( index > 0 )
{
name2 = name2.Substring( index + 1 ).Trim();
}
return string.Compare( name1 , name2 , true ) == 0 ;
}
#region 從數據庫讀取的原始數據轉 換為指定數據類型的函數群,本函數會在動態生成的派生類中使用
protected byte ConvertToByte( object v , byte DefaultValue )
{
if( v == null || DBNull.Value.Equals( v ))
return DefaultValue ;
else
return Convert.ToByte( v );
}
protected sbyte ConvertToSByte( object v , sbyte DefaultValue )
{
if( v == null || DBNull.Value.Equals( v ))
return DefaultValue ;
else
return Convert.ToSByte( v );
}
其他的 ConvertTo 函數
#endregion
/// <summary>
/// 將日期數據轉換為數據庫中的格式,本函數會在動態生成的派生類中使 用.
/// </summary>
/// <param name="Value">日期數據</param>
/// <param name="Format">保存格式化字符串</param>
/// <returns>轉換後的數據</returns>
protected object DateTimeToDBValue( DateTime Value , string Format )
{
if( Format != null || Format.Length > 0 )
{
return Value.ToString( Format );
}
else
{
return Value ;
}
}
}//public abstract class RecordORMHelper
在這個類型中,TableName屬性返回該實體對象類型綁定的 數據庫名稱,因此該屬性值由BindTableAttribute特性指定,RecordFieldNames屬性返回一 個字符串數組,該數組列出了所有的綁定的字段的名稱,也就是實體類型包含的所有的 BindFieldAttribute指定的字段名稱組成的數組。
實體類型注冊列表
在快速 ORM框架主模塊MyFastORMFramework中定義了一個myRecordHelpers的變量
private static System.Collections.Hashtable myRecordHelpers = new System.Collections.Hashtable();
這個myRecordHelpers就是實體類型 注冊列表。該列表中鍵值就是實體對象類型,而它的數據值就是一個個動態生成的從 RecordORMHelper派生的對象實例。我們定義了一個函數向該列表注冊實體對象類型
public void RegisterType( Type t )
{
if( myRecordHelpers.ContainsKey( t ) == false )
{
this.GetBindProperties( t );
myRecordHelpers[ t ] = null ;
}
}
這個過程很簡單,就是向該列表 的鍵值列表添加實體對象類型,這裡調用了GetBindProperties函數,該函數內部會仔細檢查 實體對象類型是否符合快速ORM框架的要求,若不符合則會報錯,因此這裡調用 GetBindProperties函數就是檢查實體對象類型是否合格。
ORM框架操作數據庫前都會 查詢實體類型注冊列表獲得所需的數據庫操作幫助器,也就是調用函數GetHelepr,其代碼為
private RecordORMHelper GetHelper( Type RecordType )
{
RecordORMHelper helper = null ;
if( myRecordHelpers.ContainsKey( RecordType ))
{
helper = ( RecordORMHelper ) myRecordHelpers[ RecordType ] ;
if( helper != null )
{
return helper ;
}
}
else
{
this.GetBindProperties( RecordType );
myRecordHelpers[ RecordType ] = null;
}
BuildHelpers( null );
helper = ( RecordORMHelper ) myRecordHelpers[ RecordType ] ;
if( helper == null )
{
throw new ArgumentException("為類型 " + RecordType.FullName + " 初始化系統錯誤");
}
return helper ;
}
在這個函數中, 參數就是實體對象類型,首先從注冊列表中獲得數據庫操作幫助器,若沒有找到則進行注冊 ,然後調用BuildHelpers執行動態編譯生成數據庫操作幫助器。然後再嘗試從注冊列表中獲 得數據庫操作幫助器。
在ORM框架中,GetHelper函數會頻繁的調用,因此使用實體對 象類型注冊列表可以提高系統性能。應用系統多次連續的調用RegisterType函數會導致類型 注冊列表中有多個類型對應的數據庫操作幫助器是空的,而再BuildHelpers函數內部會對所 有的沒有設定數據庫操作幫助器的實體對象類型執行動態編譯的操作,能一下子生成多個數 據庫操作幫助器,這樣能盡量減少動態編譯的次數。
代碼生成器
在動態編譯 框架中,代碼生成器是非常重要的部分。沒有代碼生成器,動態編譯框架成了無源之水,無 米之炊了。代碼生成器的主要工作就是使用反射解析數據庫實體類的結構,分析其中的數據 庫綁定信息,然後使用字符串拼湊的操作來生成C#代碼字符串。
要設計出代碼生成器 ,首先的設計出其要輸出的C#代碼的結構,我們可以不使用那個基礎的RecordORMHelper而完 全依賴生成的C#代碼來完成數據庫的映射功能,不過即使用代碼生成器,我們也得考慮到代 碼的重用,於是我們把一些通用的代碼放到RecordORMHelper中,然後動態生成的C#類就繼承 自RecordORMHelper。
ORM框架中還包含了一個IndentTextWriter的支持縮進的文本書 寫器,雖然我們可以完全使用字符串加號操作來生成代碼文本,但使用IndentTextWriter能 讓工作更高效,生成的代碼也便於人們閱讀,這有利於代碼生成器的調試和維護。在 IndentTextWriter中,使用BeginGroup來開始縮進一段代碼塊,使用EndGroup來結束縮進一 段代碼塊,使用WriteLine來輸出一行代碼文本。
在快速ORM框架中,代碼生成器包含 在函數MyFastORMFramework.GenerateCode中。現對其過程進行說明
啟用命名參數
在MyFastORMFramework中定義了NamedParameter屬性用於決定是否啟動命名參數。為 了安全,代碼生成器生成的SQL命令文本不會包含具體的數值,而是使用SQL命令參數的方式 。若設置該屬性,則啟用命名參數,此時代碼生成器生成SQL文本中使用“@參數名 ”來表示SQL命令參數占位,若沒有設置該屬性,則未啟用命名參數,此時代碼生成器 生成的SQL文本中使用“?”來表示SQL命令參數占位。比如對於新增記錄,若啟用 命令參數,則生成的SQL文本為“Insert Into Table ( Field1 , Field2 ) Values ( @Value1 , @Value2 )”,若不啟用命名參數則生成的SQL文本為“Insert Into Table( Field1 , Field2 ) Values( ? , ? )”。
某些類型的數據庫不支持無 命名的參數,有些支持,因此本快速ORM框架提供了NamedParamter屬性方法讓使用者進行調 整,使得快速ORM框架能適用於更多類型的數據庫。
生成讀取數據的代碼
基礎 類型RecordORMHelper中函數 ReadRecord調用GetFieldIndexs和InnerReadRecord函數從一個 IDataReader中讀取一行數據並創建一個實體類型的實例。GetFieldIndexs 函數用於獲得一 個整數數組,該數組的元素就是實體類各個屬性對應的數據讀取器的從0開始計算的字段欄目 序號。例如對於屬性 DB_Employees.EmployeeID,它是對象的第一個屬性成員,其綁定的字 段是“EmployeeID”。若數據讀取器的第三個欄目,也就是對它調用 IDataReader.GetName( 3 )的值是“employeeid”,則GetFieldIndexs函數返回的 數組第一個元素值就是3。若數據讀取器沒有找到和“EmployeeID”相匹配的欄目 ,則GetFieldIndexs函數返回的數組的第一個元素值是-1。使用GetFieldIndexs的返回值, ORM框架可以使用比較快速的IDataReader.GetValue( index )來讀取數據而不必使用慢速的 IDataReader.GetValue( name )了。
InnerReadRecord需要代碼生成器來生成代碼進 行擴展,對於DB_Employees,其擴展的代碼為
protected override object InnerReadRecord( System.Data.IDataReader reader , int[] FieldIndexs )
{
MyORM.DB_Employees record = new MyORM.DB_Employees();
int index = 0 ;
index = FieldIndexs[ 0 ]; // 讀取字段 EmployeeID
if( index >= 0 )
{
record.EmployeeID = ConvertToInt32( reader.GetValue( index ) , ( int ) 0) ;
}
index = FieldIndexs[ 1 ]; // 讀取字段 LastName
if( index >= 0 )
{
record.LastName = ConvertToString( reader.GetValue( index ) , null ) ;
}
讀取其他字段值 ……
return record ;
}
在這段自動生 成的代碼中,參數reader就是類型為IDataReader的數據讀取器,而FieldIndexs就是 GetFieldIndexs的返回值。在InnerReadRecord函數中會一次讀取FieldIndexs的元素值,根 據屬性的數據類型而調用ConvertToInt32,ConvertToString等一系列的ConvertTo函數,而 這一系列的函數已經在基礎類型RecordORMHelper中定義好了。
從這個自動生成的代 碼可以看出,ORM框架使用實體類的屬性,GetFieldIndexs和數據讀取器實現了如下的映射過 程
在這個過 程中,GetFieldIndexs函數提供了一個映射表,而自動生成的代碼就是利用這個映射表將數 據從DataReader復制到DB_Employees的屬性中。
我們自動生成代碼實現了 InnerReadRecord函數後,在ORM框架中就可以調用基礎的RecordORMHelper中的ReadRecord函 數讀取一行數據並生成一個實體對象,而函數ReadRecords是ReadRecord的另外一個讀取多個 數據的版本。
根據上述設計,我們可以使用以下代碼來生成InnerReadRecord代碼
myWriter.WriteLine("// 從數據讀取器讀取數據創建對象");
myWriter.WriteLine("protected override object InnerReadRecord( System.Data.IDataReader reader , int[] FieldIndexs )");
myWriter.BeginGroup("{");
myWriter.WriteLine( RecordType.FullName + " record = new " + RecordType.FullName + "();");
myWriter.WriteLine ("int index = 0 ;");
// 獲得類型中綁定到數據庫的屬性信息
for( int iCount = 0 ; iCount < ps.Length ; iCount ++ )
{
System.Reflection.PropertyInfo p = ps[ iCount ] ;
if( p.CanWrite == false )
{
continue ;
}
BindFieldAttribute fa = ( BindFieldAttribute ) Attribute.GetCustomAttribute(
p , typeof( BindFieldAttribute ));
myWriter.WriteLine ("");
myWriter.WriteLine("index = FieldIndexs[ " + iCount + " ]; // 讀取字段 " + GetBindFieldName( p ));
myWriter.WriteLine("if( index >= 0 )");
myWriter.BeginGroup("{");
Type pt = p.PropertyType ;
object DefaultValue = this.GetDefaultValue( p );
string strRead = null;
if( pt.Equals( typeof( byte )))
{
strRead = "ConvertToByte( reader.GetValue( index ) , " + GetValueString( pt , DefaultValue ) + ")";
}
else if( pt.Equals( typeof( sbyte )))
{
strRead = "ConvertToSByte( reader.GetValue( index ) , " + GetValueString( pt , DefaultValue ) + ")";
}
else if( pt.Equals( typeof( short )))
{
strRead = "ConvertToInt16( reader.GetValue( index ) , " + GetValueString( pt , DefaultValue ) + ")";
}
處理其他數據類型……
else if( pt.Equals( typeof( DateTime )))
{
string strDefault = "DateTime.MinValue" ;
DateTime dt = Convert.ToDateTime( DefaultValue );
if( dt.Equals( DateTime.MinValue ) == false )
{
strDefault = "new DateTime( " + dt.Ticks + ")";
}
strRead = "ConvertToDateTime( reader.GetValue( index ) , " + strDefault + " , " + ( fa.ReadFormat == null ? "null" : "\"" + fa.ReadFormat + "\"" ) + " )";
}
else if( pt.Equals( typeof( string )))
{
strRead = "ConvertToString( reader.GetValue( index ) , " + ( DefaultValue == null ? "null" : "@\"" + DefaultValue.ToString() + "\"" ) + " )";
}
else if( pt.Equals( typeof( char )))
{
strRead = "ConvertToChar( reader.GetValue( index ) , " + GetValueString( pt , DefaultValue ) + " )";
}
else
{
throw new InvalidOperationException("不支持屬性類型" + p.Name + " " + pt.FullName );
}
myWriter.WriteLine("record." + p.Name + " = " + strRead + " ;" );
myWriter.EndGroup("}");
}//for
myWriter.WriteLine("");
myWriter.WriteLine("return record ;");
myWriter.EndGroup(")//InnerReadRecord");
在這段代碼中, ps是一個事先分析了DB_Employees結構而得出的System.Rection.PropertyInfo數組,包含了 所有附加了BindFieldAttribute的成員屬性,它是調用GetBindProperties函數獲得的返回值 。GetDefaultValue用於獲得針對某個屬性的默認值,若調用reader.GetValue( index )獲得 了一個空值,也就是DBNull.Value則設置屬性為默認值;GetValueString是將一個數值轉換 為C#代碼的表達樣式。然後針對不同的屬性數據類型生成對應的ConvertTo代碼。
函 數GetBindProperties的代碼為
private System.Reflection.PropertyInfo[] GetBindProperties( Type RecordType )
{
if( RecordType == null )
{
throw new ArgumentNullException("ReocrdType");
}
if( RecordType.IsPublic == false )
{
throw new ArgumentException("類型 " + RecordType.FullName + " 不是公開類型");
}
if( RecordType.IsClass == false )
{
throw new ArgumentException("類型 " + RecordType.FullName + " 不是類");
}
if( RecordType.IsAbstract )
{
throw new ArgumentException("類型 " + RecordType.FullName + " 不得是抽象類");
}
// 檢查是否有可用的無參數的構造函數
// 也就是判斷語句 Record obj = new Record() 是否有效
if( RecordType.GetConstructor( new Type[]{}) == null )
{
throw new ArgumentException("類型 " + RecordType.FullName + " 沒有默認構造函數" );
}
System.Collections.ArrayList properties = new System.Collections.ArrayList ();
System.Reflection.PropertyInfo[] ps = RecordType.GetProperties(
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.Public );
foreach( System.Reflection.PropertyInfo p in ps )
{
BindFieldAttribute fa = ( BindFieldAttribute ) Attribute.GetCustomAttribute(
p , typeof( BindFieldAttribute ));
if( fa != null )
{
System.Reflection.ParameterInfo[] pps = p.GetIndexParameters();
if( pps != null && pps.Length > 0 )
{
throw new ArgumentException("屬性 " + RecordType.FullName + "." + p.Name + " 不得有參數");
}
Type pt = p.PropertyType ;
if( pt.IsPrimitive || pt.Equals( typeof( string )) || pt.Equals( typeof( DateTime )))
{
}
else
{
throw new ArgumentException("不支持屬性 " + RecordType.FullName + "." + p.Name + " 的數據類型 " + pt.FullName );
}
properties.Add( p );
}
}
if( properties.Count == 0 )
{
throw new ArgumentException("類型 " + RecordType.FullName + " 沒有標記為綁定到任何字段");
}
return ( System.Reflection.PropertyInfo[] ) properties.ToArray( typeof( System.Reflection.PropertyInfo ));
}
從這個函數可以看 出,快速ORM框架處理的實體類型必須是一個類型,必須公開,不得是抽象的,而且有公開的 無參數的構造函數。這裡使用了.NET框架的反射技術,首先使用Type.GetConstructor函數獲 得對象類型指定樣式的構造函數對象,還使用GetProperties函數獲得實體類型的所有的公開 實例屬性。若屬性附加了BindFieldAttribute特性則添加到輸出列表中。注意,屬性的數據 類型必須是CLR基礎數據類型,字符串或者時間日期格式,其他的數據類型是不合要求的。
這裡還調用了一個GetBindFieldName獲得屬性綁定的數據庫字段名,其代碼為
private string GetBindFieldName( System.Reflection.PropertyInfo p )
{
BindFieldAttribute fa = ( BindFieldAttribute ) Attribute.GetCustomAttribute(
p , typeof( BindFieldAttribute ));
string name = fa.Name ;
if( name != null )
name = name.Trim();
if( name == null || name.Length == 0 )
name = p.Name ;
return name ;
}
其功能很簡單,就是 檢查屬性是否附加了BindFieldAttribute特性,若附加了則使用該特性的Name值,若沒有則 直接返回屬性的名稱。
生成插入數據的代碼
基礎類型RecordORMHelper預留了 FillInsertCommand函數,該函數就是為插入數據庫記錄而設置數據庫命令對象(IDbCommand) 的。對於DB_Employees,其代碼為
public override int FillInsertCommand( System.Data.IDbCommand cmd , object objRecord )
{
if( cmd == null ) throw new ArgumentNullException ("cmd");
if( objRecord == null ) throw new ArgumentNullException("objRecord");
MyORM.DB_Employees myRecord = objRecord as MyORM.DB_Employees ;
if( myRecord == null ) throw new ArgumentException("must type 'MyORM.DB_Employees' ");
System.Collections.ArrayList myFieldNames = new System.Collections.ArrayList();
System.Collections.ArrayList myValues = new System.Collections.ArrayList();
myFieldNames.Add( "EmployeeID" );
myValues.Add( myRecord.EmployeeID );
if( myRecord.LastName != null )
{
myFieldNames.Add( "LastName" );
myValues.Add( myRecord.LastName );
}
myFieldNames.Add( "BirthDate" );
myValues.Add( myRecord.BirthDate.ToString("yyyy-MM-dd") );
處理其他屬性值 ……
myFieldNames.Add( "Sex" );
myValues.Add( myRecord.Sex );
if( myFieldNames.Count == 0 ) return 0 ;
cmd.Parameters.Clear() ;
System.Text.StringBuilder mySQL = new System.Text.StringBuilder();
mySQL.Append( "Insert Into Employees ( " );
mySQL.Append( ConcatStrings( myFieldNames ));
mySQL.Append( " ) Values ( " );
for( int iCount = 0 ; iCount < myValues.Count ; iCount ++ )
{
if( iCount > 0 ) mySQL.Append(" , " );
mySQL.Append(" ? ") ;
System.Data.IDbDataParameter parameter = cmd.CreateParameter ();
parameter.Value = myValues[ iCount ] ;
cmd.Parameters.Add( parameter );
}//for
mySQL.Append( " ) " );
cmd.CommandText = mySQL.ToString();
return myValues.Count ;
}
在 這段代碼中,首先是檢查參數是否正確。然後處理實體類型的所有的屬性。若屬性值等於默 認值則跳過處理,否則將屬性綁定的字段的名稱保存到myFieldNames列表中,屬性值保存到 myValues列表中。最後使用字符串拼湊的操作來生成SQL命令文本,若NamedParameter屬性為 Ture,則生成的SQL文本為“Insert Into TableName ( FieldName1 , FieldName2 … ) Values ( @Value1 , @Value2 …)”,若該屬性為False,則生成的 SQL文本為“Insert Into TableName ( FieldName1 , FieldName2 … ) Values ( ? , ? …)”,並將屬性值添加到數據庫命令對象的參數列表中,該 函數返回保存數據的屬性的個數。
本文配套源碼