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

C#發現之旅第十二講 基於反射和動態編譯的快速ORM框架(下)

編輯:關於C#

對於字符串類型的屬性,其默認值就是“DBNull”。而對於其他的整數或者日 期類型的屬性,並沒有默認值,因此是無條件的插入到數據庫中。

我們使用以下的代 碼來生成上述代碼文本

myWriter.WriteLine("public override int  FillInsertCommand( System.Data.IDbCommand cmd , object objRecord )");
myWriter.BeginGroup("{");
myWriter.WriteLine("if( cmd == null ) throw  new ArgumentNullException(\"cmd\");");
myWriter.WriteLine("if( objRecord  == null ) throw new ArgumentNullException(\"objRecord\");");
myWriter.WriteLine(RecordType.FullName + " myRecord = objRecord as " +  RecordType.FullName + " ;");
myWriter.WriteLine("if( myRecord ==  null ) throw new ArgumentException(\"must type '" + RecordType.FullName  + "' \");");
myWriter.WriteLine("System.Collections.ArrayList  myFieldNames = new System.Collections.ArrayList();");
myWriter.WriteLine ("System.Collections.ArrayList myValues = new System.Collections.ArrayList ();");
for (int iCount = 0; iCount < ps.Length; iCount++)
{
    System.Reflection.PropertyInfo p = ps[iCount];
    if  (p.CanRead == false)
    {
        continue;
     }

    BindFieldAttribute fa = (BindFieldAttribute) Attribute.GetCustomAttribute(
        p, typeof (BindFieldAttribute));
    string FieldName = GetBindFieldName(p);

    myWriter.WriteLine("");
    Type pt =  p.PropertyType;
    object DefaultValue = this.GetDefaultValue(p);
    if (pt.Equals(typeof(string)))
    {
         myWriter.WriteLine("if( myRecord." + p.Name + " != null &&  myRecord." + p.Name + ".Length != 0 )");
         myWriter.BeginGroup("{");
        myWriter.WriteLine ("myFieldNames.Add( \"" + FieldName + "\" );");
         myWriter.WriteLine("myValues.Add( myRecord." + p.Name + " );");
         myWriter.EndGroup("}");
    }
    else if  (pt.Equals(typeof(DateTime)))
    {
         myWriter.WriteLine("myFieldNames.Add( \"" + FieldName + "\" );");
         if (fa.WriteFormat != null && fa.WriteFormat.Length  > 0)
        {
            myWriter.WriteLine ("myValues.Add( myRecord." + p.Name + ".ToString(\"" + fa.WriteFormat +  "\") );");
        }
        else
         {
            myWriter.WriteLine("myValues.Add( myRecord." +  p.Name + ".ToString(\"yyyy-MM-dd HH:mm:ss\") );");
        }

    }
    else
    {
         myWriter.WriteLine("myFieldNames.Add( \"" + FieldName + "\" );");
         myWriter.WriteLine("myValues.Add( myRecord." + p.Name +  " );");
    }
}//for
myWriter.WriteLine("");
myWriter.WriteLine("if( myFieldNames.Count == 0 ) return 0 ;");
myWriter.WriteLine("cmd.Parameters.Clear() ;");
myWriter.WriteLine ("System.Text.StringBuilder mySQL = new System.Text.StringBuilder();");
myWriter.WriteLine("mySQL.Append( \"Insert Into " + TableName + " ( \"  );");
myWriter.WriteLine("mySQL.Append( ConcatStrings( myFieldNames  ));");
myWriter.WriteLine("mySQL.Append( \" ) Values ( \" );");
myWriter.WriteLine("for( int iCount = 0 ; iCount < myValues.Count ;  iCount ++ )");
myWriter.BeginGroup("{");
myWriter.WriteLine("if(  iCount > 0 ) mySQL.Append(\" , \" );");
if (bolNamedParameter)
{
    myWriter.WriteLine("mySQL.Append(\" @Value\" + iCount )  ;");
    myWriter.WriteLine("System.Data.IDbDataParameter parameter  = cmd.CreateParameter();");
    myWriter.WriteLine("parameter.Value =  myValues[ iCount ] ;");
    myWriter.WriteLine ("parameter.ParameterName = \"Value\" + iCount ;");
     myWriter.WriteLine("cmd.Parameters.Add( parameter );");
}
else
{
    myWriter.WriteLine("mySQL.Append(\" ? \") ;");
     myWriter.WriteLine("System.Data.IDbDataParameter parameter =  cmd.CreateParameter();");
    myWriter.WriteLine("parameter.Value =  myValues[ iCount ] ;");
    myWriter.WriteLine("cmd.Parameters.Add(  parameter );");
}
myWriter.EndGroup("}//for");
myWriter.WriteLine("mySQL.Append( \" ) \" );");
myWriter.WriteLine ("cmd.CommandText = mySQL.ToString();");
myWriter.WriteLine("return  myValues.Count ;");
myWriter.EndGroup(")//public override int  FillInsertCommand( System.Data.IDbCommand cmd , object objRecord  )");

在這裡我們首先輸出檢查參數的代碼文本,然後遍歷所有綁定字段的 屬性對象,根據屬性的數據類型分為字符串樣式,日期樣式和其他樣式。對於字符串樣式則 需要輸出判斷是否為空的代碼,對於日期樣式則還要考慮BindFieldAttribute特性中指明的 數據保存樣式,對於其他樣式則沒有任何判斷,直接輸出。

生成刪除數據的代碼

基礎類型RecordORMHelper預留了FillDeleteCommand函數,代碼生成器自動生成代碼 來實現FillDeleteCommand函數,而ORM框架就會創建一個數據庫命令對象,然後調用 FillDeleteCommand函數來為刪除數據而初始化數據庫命令對象,然後執行SQL命令刪除數據 。

在DB_Employees中使用一下代碼來定義EmployeeID屬性的。

///<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;
     }
}

附加的BindField特性中使用了“Key=true”指明了EmployeeID字段是關鍵字段。 於是我們很容易就想到使用SQL語句“Delete From Employees Where EmployeeID=指定 的員工編號”來刪除數據。於是針對DB_Employees代碼生成器生成的代碼如下

public override int FillDeleteCommand( 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' ");
    cmd.Parameters.Clear();
     cmd.CommandText = @"Delete From Employees Where EmployeeID = ? " ;
    System.Data.IDbDataParameter parameter = null ;

     parameter = cmd.CreateParameter();
    parameter.Value =  myRecord.EmployeeID ;
    cmd.Parameters.Add( parameter );

    return 1 ;
}

我們可以使用以下代碼來生成上述的C#代碼 文本。

// 關鍵字段SQL參數名稱列表
System.Collections.ArrayList  KeyParameterNames = new System.Collections.ArrayList();
// 生成Where子 語句文本
System.Text.StringBuilder myWhereSQL = new  System.Text.StringBuilder();
System.Collections.ArrayList KeyProperties =  new System.Collections.ArrayList();
for (int iCount = 0; iCount  < ps.Length; iCount++)
{
    System.Reflection.PropertyInfo p  = ps[iCount];
    if (p.CanRead == false)
    {
         continue;
    }
    BindFieldAttribute fa =  (BindFieldAttribute)Attribute.GetCustomAttribute(
        p,  typeof(BindFieldAttribute));
    if (fa.Key == false)
     {
        continue;
    }

    string  FieldName = this.GetBindFieldName(p);
    if (myWhereSQL.Length >  0)
    {
        myWhereSQL.Append(" and ");
     }
    KeyProperties.Add(p);
    if (bolNamedParameter)
    {
        string pName = "Key" + p.Name;
         KeyParameterNames.Add(pName);
        myWhereSQL.Append (FixFieldName(FieldName) + " = @" + pName + " ");
    }
     else
    {
        myWhereSQL.Append(FixFieldName (FieldName) + " = ? ");
    }
}//for

myWriter.WriteLine("public override int FillDeleteCommand(  System.Data.IDbCommand cmd , object objRecord )");
myWriter.BeginGroup ("{");
if (KeyProperties.Count == 0)
{
     myWriter.WriteLine("throw new NotSupportedException (\"FillDeleteCommand\");");
}
else
{
     myWriter.WriteLine("if( cmd == null ) throw new ArgumentNullException (\"cmd\");");
    myWriter.WriteLine("if( objRecord == null ) throw  new ArgumentNullException(\"objRecord\");");
    myWriter.WriteLine (RecordType.FullName + " myRecord = objRecord as " + RecordType.FullName  + " ;");
    myWriter.WriteLine("if( myRecord == null ) throw  new ArgumentException(\"must type '" + RecordType.FullName + "'  \");");
    System.Text.StringBuilder myDeleteSQL = new  System.Text.StringBuilder();
    myDeleteSQL.Insert(0, "Delete From "  + TableName + " Where " + myWhereSQL.ToString());
     myWriter.WriteLine("cmd.Parameters.Clear();");
    myWriter.WriteLine ("cmd.CommandText = @\"" + myDeleteSQL.ToString() + "\" ;");
     myWriter.WriteLine("System.Data.IDbDataParameter parameter = null ;");
    int index = 0;
    foreach (System.Reflection.PropertyInfo  p in KeyProperties)
    {
        myWriter.WriteLine ("");
        myWriter.WriteLine("parameter = cmd.CreateParameter ();");
        WriteSetParameterValue(p, myWriter);
         if (bolNamedParameter)
        {
             myWriter.WriteLine("parameter.ParameterName = \"" + KeyParameterNames [index] + "\";");
        }
         myWriter.WriteLine("cmd.Parameters.Add( parameter );");
         index++;
    }
    myWriter.WriteLine("");
     myWriter.WriteLine("return " + KeyProperties.Count + " ;");

}

myWriter.EndGroup(")");

在這段代碼中,首先是遍歷實體類型中所 有的綁定到字段的屬性,根據其附加的BindFieldAttribute特性的Key值找到所有的關鍵字段 屬性對象,並創建了一個“關鍵字段名1=@Key屬性名1 And 關鍵字段名2=@Key屬性名 2”(若未啟用命名參數則為“關鍵字段名1=? And 關鍵字段名2=? ……”)格式的字符串,該字符串就是SQL語句的Where子語句了,若啟用 命名參數則生成的文本為。

若沒有找到任何關鍵屬性,則無法確定刪除記錄使用的查 詢條件,此時代碼生成器就會輸出拋出異常的代碼。在這裡代碼生成器不會直接拋出異常而 導致ORM框架過早的報警,未來可能有開發人員定義的實體類型只是為了查詢或者新增數據庫 記錄,那時不需要定義關鍵屬性。若對這種實體類型過早的報警則減少了快速ORM框架的使用 范圍。

若實體類型定義了一個或者多個關鍵屬性,則開始輸出代碼文本,首先輸出檢 查參數的代碼文本,然後遍歷所有的關鍵屬性對象,生成向數據庫命令對象添加參數的代碼 。

這裡還用到了一個WriteSetParamterValue的方法用於書寫設置參數值的過程,其 代碼為

private void WriteSetParameterValue(  System.Reflection.PropertyInfo p , IndentTextWriter myWriter )
{
    if (p.PropertyType.Equals(typeof(DateTime)))
    {
         BindFieldAttribute fa = (BindFieldAttribute) Attribute.GetCustomAttribute(
            p, typeof (BindFieldAttribute));
        if (fa.WriteFormat == null ||  fa.WriteFormat.Length == 0)
        {
             myWriter.WriteLine("parameter.Value = myRecord." + p.Name + ".ToString (\"yyyy-MM-dd HH:mm:ss\");");
        }
         else
        {
            myWriter.WriteLine ("parameter.Value = myRecord." + p.Name + ".ToString(\"" + fa.WriteFormat  + "\");");
        }
    }
    else if  (p.PropertyType.Equals(typeof(string)))
    {
         myWriter.WriteLine("if( myRecord." + p.Name + " == null || myRecord." +  p.Name + ".Length == 0 )");
        myWriter.WriteLine("    parameter.Value = System.DBNull.Value ;");
         myWriter.WriteLine("else");
        myWriter.WriteLine("    parameter.Value = myRecord." + p.Name + " ;");
    }
     else
    {
        myWriter.WriteLine("parameter.Value =  myRecord." + p.Name + " ;");
    }
}

該方法內判 斷若屬性數據類型為時間型則設置輸出的數據格式,若為字符串類型,則判斷數據是否為空 ,若為空則設置參數值為DBNull。

生成更新數據的代碼

基礎類型 RecordORMHelper預留了FillUpdateCommand函數,快速ORM框架更新數據庫時首先創建一個數 據庫命令對象,然後調用FillUpdateCommand函數設置SQL語句,添加SQL參數,然後執行該命 令對象接口更新數據庫記錄。對於DB_Employees,其FillUpdateCommand函數的代碼為

public override int FillUpdateCommand( 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' ");

    cmd.CommandText = @"Update  Employees Set EmployeeID = ?  , LastName = ?  , FirstName = ?  ,  Title = ?  , TitleOfCourtesy = ?  , Address = ?  , BirthDate =  ?  , City = ?  , Country = ?  , EducationalLevel = ?  , EMail  = ?  , Extension = ?  , Goal = ?  , HireDate = ?  , HomePage  = ?  , HomePhone = ?  , Notes = ?  , PostalCode = ?  ,  Region = ?  , ReportsTo = ?  , Sex = ?  Where EmployeeID = ? "  ;
    cmd.Parameters.Clear();
     System.Data.IDbDataParameter parameter = null ;

    parameter  = cmd.CreateParameter();
    parameter.Value = myRecord.EmployeeID  ;
    cmd.Parameters.Add( parameter );

    parameter  = cmd.CreateParameter();
    parameter.Value =  myRecord.BirthDate.ToString("yyyy-MM-dd");
    cmd.Parameters.Add(  parameter );
    為其他屬性值添加SQL參數對象。。。。。。
     parameter = cmd.CreateParameter();
    parameter.Value =  myRecord.Sex ;
    cmd.Parameters.Add( parameter );
    //這 裡為查詢條件添加參數
    parameter = cmd.CreateParameter();
     parameter.Value = myRecord.EmployeeID ;
    cmd.Parameters.Add(  parameter );

    return 22 ;
}

這段代碼結構比 較簡單,首先是對參數進行判斷,然後設置SQL更新語句,然後將所有的屬性的值依次添加到 SQL參數列表中,最後還為查詢將EmployeeID值添加到SQL參數列表中。

在代碼生成器 中生成FillUpdateCommand代碼文本的代碼為

myWriter.WriteLine("public  override int FillUpdateCommand( System.Data.IDbCommand cmd , object  objRecord )");
myWriter.BeginGroup("{");
if (KeyProperties.Count ==  0)
{
    myWriter.WriteLine("throw new NotSupportedException (\"FillUpdateCommand\");");
}
else
{
     myWriter.WriteLine("if( cmd == null ) throw new ArgumentNullException (\"cmd\");");
    myWriter.WriteLine("if( objRecord == null ) throw  new ArgumentNullException(\"objRecord\");");
    myWriter.WriteLine (RecordType.FullName + " myRecord = objRecord as " + RecordType.FullName  + " ;");
    myWriter.WriteLine("if( myRecord == null ) throw  new ArgumentException(\"must type '" + RecordType.FullName + "'  \");");
    // 更新用SQL語句文本
    System.Text.StringBuilder  myUpdateSQL = new System.Text.StringBuilder();
    // 所有的SQL參 數名稱
    System.Collections.ArrayList ParameterNames = new  System.Collections.ArrayList();
    foreach  (System.Reflection.PropertyInfo p in ps)
    {
         if (p.CanRead == false)
        {
             continue;
        }
        string FieldName =  this.GetBindFieldName(p);
        if (myUpdateSQL.Length > 0)
        {
            myUpdateSQL.Append(" ,  ");
        }
        if (bolNamedParameter)
         {
            string pName = "Value" +  p.Name;
            ParameterNames.Add( pName );
             myUpdateSQL.Append(FixFieldName(FieldName) + " = @" +  pName);
        }
        else
         {
            myUpdateSQL.Append(FixFieldName(FieldName) + "  = ? ");
        }
    }//foreach
     ParameterNames.AddRange(KeyParameterNames);
    myUpdateSQL.Insert(0,  "Update " + FixTableName(TableName) + " Set ");
     myUpdateSQL.Append(" Where " + myWhereSQL.ToString());

     myWriter.WriteLine("");
    myWriter.WriteLine("cmd.CommandText = @\""  + myUpdateSQL.ToString() + "\" ;");
    myWriter.WriteLine ("cmd.Parameters.Clear();");
    myWriter.WriteLine ("System.Data.IDbDataParameter parameter = null ;");
     myWriter.WriteLine("");
    System.Collections.ArrayList ps2 = new  System.Collections.ArrayList();
    ps2.AddRange(ps);
     ps2.AddRange(KeyProperties);

    foreach  (System.Reflection.PropertyInfo p in ps2)
    {
         if (p.CanRead == false)
        {
             continue;
        }
        myWriter.WriteLine("");
        myWriter.WriteLine("parameter = cmd.CreateParameter();");
        WriteSetParameterValue(p, myWriter);
        if  (bolNamedParameter)
        {
            // 設置 SQL命令對象的名稱
            myWriter.WriteLine ("parameter.ParameterName = \"" + ParameterNames[0] + "\";");
             ParameterNames.RemoveAt(0);
        }
         myWriter.WriteLine("cmd.Parameters.Add( parameter );");
     }//foreach
    myWriter.WriteLine("");
     myWriter.WriteLine("return " + ps2.Count + " ;");
}//else
myWriter.EndGroup(")//public override int FillUpdateCommand(  System.Data.IDbCommand cmd , object objRecord )");

這裡的 KeyProperties,KeyParameterNames和myWhereSQL的值都在生成FillDeleteCommand時已經設 置好了,這裡直接拿來用。若KeyProperties沒有內容,說明實體類型沒有指明綁定了關鍵字 段的屬性,此時無法生成更新時的查詢語句,於是輸出拋出異常的C#代碼文本。

我們 首先遍歷實體類型中所有的綁定了字段的屬性對象,拼湊出“Update TableName Set 字段名1=@Value屬性名1 , 字段名2=@Value屬性名2”(若未啟用命名參數則輸出為 “Update TableName Set 字段名1=? , 字段名2=?”)樣式的SQL文本,然後加上 myWhereSQL中的查詢條件文本,從而得出了完整的SQL語句,然後將其輸出到代碼文本中。

我們有一次遍歷實體類型所有綁定了字段的屬性對象,對於每一個屬性輸出添加SQL 參數對象的C#代碼文本。此外還遍歷KeyProperties來生成添加查詢條件SQL參數的C#代碼文 本。

函數最後返回添加的SQL參數個數的返回語句。

生成完整的C#源代碼文本

在實現了生成讀取數據,插入數據,刪除數據和更新數據的代碼文本的程序代碼後, 我們就可以實現完整的生成C#代碼文本的程序代碼了,這些程序代碼就是方法GenerateCode 的全部內容,其代碼為

private string GenerateCode( string nsName  , string strFileName , System.Collections.ArrayList RecordTypes )
{
    // 開始創建代碼
    IndentTextWriter myWriter = new  IndentTextWriter();
    myWriter.WriteLine("using System;");
     myWriter.WriteLine("using System.Data;");
    myWriter.WriteLine ("namespace " + nsName);
    myWriter.BeginGroup("{");
     // 對每一個數據容器對象創建數據處理類的代碼
    foreach (Type  RecordType in RecordTypes)
    {
        string  TableName = RecordType.Name;
        BindTableAttribute ta =  (BindTableAttribute)Attribute.GetCustomAttribute(
             RecordType, typeof(BindTableAttribute), false);
        if (ta  != null)
        {
            TableName =  ta.Name;
        }
        if (TableName == null ||  TableName.Trim().Length == 0)
        {
             TableName = RecordType.Name;
        }
         TableName = TableName.Trim();

         System.Reflection.PropertyInfo[] ps = this.GetBindProperties(RecordType);
        myWriter.WriteLine("public class " + RecordType.Name +  "ORMHelper : " + typeof(RecordORMHelper).FullName);
         myWriter.BeginGroup("{");
        myWriter.WriteLine("");
         myWriter.WriteLine("///<summary>創建對象</summary>");
        myWriter.WriteLine("public " + RecordType.Name +  "ORMHelper(){}");
        myWriter.WriteLine("");

生成 重載TableName的代碼
生成重載RecordFieldNames的代碼
生成重載 FillUpdateCommand的代碼
生成重載FillDeleteCommand的代碼
生成重載 FillInsertCommand的代碼
生成重載InnerReadRecord的代碼

     }//foreach
    myWriter.EndGroup("}//namespace");

     // 若需要保存臨時生成的C#代碼到指定的文件
    if (strFileName !=  null && strFileName.Length > 0)
    {
         myWriter.WriteFile(strFileName, System.Text.Encoding.GetEncoding(936));
    }
    return myWriter.ToString();
}

這個函數 的參數是生成的代碼的名稱空間的名稱,保存代碼文本的文件名和要處理的數據庫實體對象 類型列表。在函數中首先創建一個myWriter的代碼文本書寫器,輸出導入名稱空間的代碼文 本,輸出命名空間的代碼文本,然後遍歷RecordTypes列表中的所有的實體對象類型,對每一 個實體對象類型輸出一個定義類的C#代碼文本,類名就是 類型名稱+ORMHelper,該類繼承自 RecordORMHelper類型。然後執行上述的生成TableName,RecordFieldNames, FillUpdateCommand,FillDelteCommand,FillInsertCommand和InnerReadRecord的C#代碼文 本的過程,這樣就完成了針對一個實體對象類型的C#代碼的生成過程。

當代碼生成器 完成工作後,內置的代碼文本書寫器myWriter中就包含了完整的C#代碼文本。這個代碼文本 中包含了多個從RecordORMHelper類型派生的數據庫操作幫助類型。這樣我們就可以隨即展開 動態編譯的操作了。

動態編譯

在代碼生成器成功的生成所有的C#源代碼文本 後,我們就可以執行動態編譯了,函數MyFastORMFramework.BuildHelpers就是實現動態編譯 ,其代碼為

private int BuildHelpers( string strFileName )
{
     System.Collections.ArrayList RecordTypes = new  System.Collections.ArrayList();
     foreach( Type RecordType in  myRecordHelpers.Keys )
     {
         if(  myRecordHelpers[ RecordType ] == null )
         {
               RecordTypes.Add( RecordType );
         }
     }//foreach
     if( RecordTypes.Count == 0 )
         return 0 ;

     // 開始創建代碼
      string nsName = "Temp" + System.Guid.NewGuid().ToString("N");
     // 生成C#代碼
    string strSource = GenerateCode(nsName,  strFileName , RecordTypes );

     // 編譯臨時生成的C#代碼
     System.Collections.Specialized.StringCollection strReferences =  new System.Collections.Specialized.StringCollection();

      System.CodeDom.Compiler.CompilerParameters options = new  System.CodeDom.Compiler.CompilerParameters();
      options.GenerateExecutable = false;
     options.GenerateInMemory =  true ;
     // 添加編譯器使用的引用
      System.Collections.ArrayList refs = new System.Collections.ArrayList();
     foreach( Type t in RecordTypes )
     {
          refs.Add( t.Assembly.CodeBase );
     }
      refs.Add( this.GetType().Assembly.CodeBase );
     refs.AddRange(  new string[]{
                                         "mscorlib.dll",
                                         "System.dll" ,
                                          "System.Data.dll" ,
     });
     for( int iCount = 0  ; iCount < refs.Count ; iCount ++ )
     {
          string strRef = ( string ) refs[ iCount ] ;
          if( strRef.StartsWith("file:///"))
              strRef  = strRef.Substring( "file:///".Length );
         if(  options.ReferencedAssemblies.Contains( strRef ) == false )
          {
              options.ReferencedAssemblies.Add(  strRef );
         }
     }

      //string strSource = myWriter.ToString();
     // 調用C#代碼編譯 器編譯生成程序集

     Microsoft.CSharp.CSharpCodeProvider  provider = new Microsoft.CSharp.CSharpCodeProvider();
     // 若使 用微軟.NET框架.1則調用ICodeCompiler
      //System.CodeDom.Compiler.ICodeCompiler compiler = provider.CreateCompiler ();
     //System.CodeDom.Compiler.CompilerResults result =  compiler.CompileAssemblyFromSource( options , strSource );
     //  若使用VS.NET2005或更新版本編譯程序會在這裡形成一個編譯警告信息,
      // 則可以將上面兩行代碼去掉而使用下面的代碼
     System.CodeDom.Compiler.CompilerResults result =  provider.CompileAssemblyFromSource(options, strSource);

     if(  result.Errors.Count == 0 )
     {
          System.Reflection.Assembly asm = result.CompiledAssembly ;
          myAssemblies.Add( asm );

         // 創建內置的數據 庫對象操作對象
         foreach( Type RecordType in RecordTypes  )
         {
              Type t =  asm.GetType( nsName + "." + RecordType.Name + "ORMHelper" );
               RecordORMHelper helper = ( RecordORMHelper )  System.Activator.CreateInstance( t );
               myRecordHelpers[ RecordType ] = helper ;
               System.Console.WriteLine("FastORM為\"" + RecordType.FullName + "\"創建操作幫 助對象");
         }
     }
     else
      {
         System.Console.WriteLine("ORM框架動態編譯錯 誤" );
         foreach( string strLine in result.Output )
         {
              System.Console.WriteLine(  strLine );
         }
     }
      provider.Dispose();

     return RecordTypes.Count ;
}

在本函數中,我們遍歷實體Lexington注冊列表,找到所有沒有裝備數據庫操 作幫助器的實體類型,添加到RecordTypes列表中,然後調用GenerateCode函數生成C#代碼。

我們確定編譯過程要引用的程序集,Mscorlib.dll,System.dll,System.Data.dll 是基本的必不可少的引用,所有的參與動態編譯的實體對象類型所在的程序集也得引用,快 速ORM框架本身所在的程序集也得引用。將所有的引用信息添加到options的 ReferencedAssemblies列表中,這裡的options變量是編譯使用的參數。然後我們使用 myWriter.ToString()獲得代碼生成器生成的C#源代碼文本。我們創建一個 CSharpCodeProvider對象,准備編譯了,對於微軟.NET框架1.1和2.0其調用過程是不同的。 對於微軟.NET框架1.1,其調用過程為

Microsoft.CSharp.CSharpCodeProvider  provider = new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.ICodeCompiler compiler = provider.CreateCompiler ();
System.CodeDom.Compiler.CompilerResults result =  compiler.CompileAssemblyFromSource( options , strSource );

而對 微軟.NET框架2.0其調用過程為

Microsoft.CSharp.CSharpCodeProvider  provider = new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.CompilerResults result =  provider.CompileAssemblyFromSource(options, strSource);

這體現了微 軟.NET框架1.1和2.0之間的差別。但微軟.NET框架2.0是兼容1.1的,因此用於微軟.NET1.1的 代碼可以在微軟.NET2.0下編譯通過,但編譯器會提示警告信息。

這裡調用 CompileAssemblyFromSource實際上就是調用微軟.NET框架中的基於命令行的C#程序編譯器 csc.exe的封裝。其內部會根據編譯器參數options保存的信息生成命令行文本然後啟動 csc.exe進程。然後將csc.exe的輸出結果保存在CompilerResults對象中。

若一切順 利,則使用CompilerResults.CompiledAssembly就能獲得編譯後生成的程序集,然後我們使 用反射操作,對每一個實體類型從動態編譯生成的程序集中獲得對應的數據庫幫助器的類型 ,然後使用System.Activator.CreateInstance函數就能實例化一個數據庫操作幫助器,將這 個幫助器放置在實體類型注冊列表中等待下次選用。

操作數據庫

我們使用動 態編譯技術獲得了數據庫操作幫助器,現在我們就使用這些幫助器來實現高速的ORM操作。

查詢數據 ReadObjects

快速ORM框架中,定義了一個ReadObjects的函數,用 於從數據庫中讀取數據並生成若干個實體對象,其代碼為

public  System.Collections.ArrayList ReadObjects( string strSQL , Type RecordType  )
{
     this.CheckConnection();
     if( strSQL ==  null )
     {
         throw new  ArgumentNullException("strSQL");
     }
     if( RecordType  == null )
     {
         throw new  ArgumentNullException("RecordType");
     }
      RecordORMHelper helper = this.GetHelper( RecordType );
     using(  System.Data.IDbCommand cmd = this.Connection.CreateCommand())
      {
         cmd.CommandText = strSQL ;
          System.Data.IDataReader reader = cmd.ExecuteReader();
          System.Collections.ArrayList list = helper.ReadRecords( reader , 0 );
         reader.Close();
         return list ;
     }
}

private void CheckConnection()
{
      if( myConnection == null )
     {
         throw  new InvalidOperationException("Connection is null");
     }
     if( myConnection.State != System.Data.ConnectionState.Open )
     {
         throw new InvalidOperationException ("Connection is not opened");
     }
}

這個函數的 參數是SQL查詢語句和實體對象類型。在這個函數中,首先是調用CheckConnection函數來檢 查數據庫的連接狀態,然後使用GetHelper函數獲得對應的數據庫操作幫助類,然後執行SQL 查詢,獲得一個數據庫讀取器,然後調用數據操作幫助類的ReadRecords獲得一個列表,該列 表就包含了查詢數據所得的實體對象。這個過程沒有使用反射,執行速度非常快,使用這個 快速ORM框架,執行速度跟我們傳統的手工編寫代碼創建實體對象的速度是一樣的,但大大降 低了我們的開發工作量。

在快速ORM框架中,根據ReadObjects函數派生了ReadObject ,ReadAllObject等系列讀取數據的函數,其原理都是一樣的。

刪除數據 DeleteObject

在快速ORM框架中定義了一個DeleteObject函數用於刪除數據,其代碼 為

public int DeleteObject( object RecordObject )
{
      this.CheckConnection();
     if( RecordObject == null )
     {
         throw new ArgumentNullException ("RecordObject");
     }
     RecordORMHelper helper =  this.GetHelper( RecordObject.GetType() );
     using(  System.Data.IDbCommand cmd = this.Connection.CreateCommand())
      {
         if( helper.FillDeleteCommand( cmd , RecordObject )  > 0 )
         {
              return  cmd.ExecuteNonQuery();
         }
     }
      return 0 ;
}

這個函數的參數就是要刪除的對象,在函數中,首先調 用GetHelper函數獲得數據操作幫助器,然後創建一個數據庫命令對象,調用幫助類的 FillDeleteCommand函數初始化數據庫命令對象,然後執行該命令對象即可刪除數據,過程簡 單明了。ORM框架還定義了DeleteObjects函數用於刪除多個實體對象,其原理和 DeleteObject函數一樣。

更新數據 UpdateObject

快速ORM框架定義了 UpdateObject函數用於更新數據,其代碼為

public int UpdateObject(  object RecordObject )
{
     this.CheckConnection();
      if( RecordObject == null )
     {
         throw  new ArgumentNullException("RecordObject");
     }
      RecordORMHelper helper = this.GetHelper( RecordObject.GetType());
      using( System.Data.IDbCommand cmd = this.Connection.CreateCommand())
     {
         int fields = helper.FillUpdateCommand(  cmd , RecordObject );
         if( fields > 0 )
          {
              return cmd.ExecuteNonQuery ();
         }
     }
     return 0 ;
}

過程很簡單,首先使用GetHelepr函數獲得數據庫幫助器,然後調用它的 FillUpdateCommand函數來設置數據庫命令對象,然後執行數據庫命令對象即可完成刪除數據 的操作。ORM框架還定義了 UpdateObjects函數用於更新多條數據庫記錄,其原理和 UpdateObject函數是一樣的。

新增數據 InsertObject

快速ORM框架定義了 InsertObject函數用於新增數據庫記錄,其代碼為

public int  InsertObject( object RecordObject )
{
      this.CheckConnection();
     if( RecordObject == null )
      {
         throw new ArgumentNullException ("RecordObject");
     }
     RecordORMHelper helper =  this.GetHelper( RecordObject.GetType());
     using(  System.Data.IDbCommand cmd = this.Connection.CreateCommand())
      {
         int fields = helper.FillInsertCommand( cmd ,  RecordObject );
         if( fields > 0 )
          {
              return cmd.ExecuteNonQuery();
          }
     }//using
     return 0 ;
}

這個函數也很簡單,使用GetHelper獲得數據庫幫助器,調用幫助器的 FillInsertCommand函數設置數據庫命令對象,然後執行它即可向數據庫插入一條記錄。另外 一個InsertObjects函數用於插入多條數據庫記錄,其原理是一樣的。

使用ORM框架

在這裡我們建立一個簡單的WinForm程序來測試使用快速ORM框架。首先我們在一個 Access數據庫中建立一個員工信息表,名稱為Empolyees,並相應的定義了一個數據庫實體類 型DB_Employees。然後畫出一個窗口放置一些控件,編寫一些代碼,運行程序,其運行界面 為

該演示程序主要代碼為

/// <summary>
/// 連接數 據庫,創建快速ORM框架對象
/// </summary>
/// <returns>ORM 框架對象</returns>
private MyFastORMFramework CreateFramework()
{
     System.Data.OleDb.OleDbConnection conn = new  System.Data.OleDb.OleDbConnection();
     conn.ConnectionString =  "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + System.IO.Path.Combine(  System.Windows.Forms.Application.StartupPath , "demomdb.mdb" );
      conn.Open();
     return new MyFastORMFramework( conn );
}

// 刷新按鈕事件處理
private void cmdRefresh_Click(object  sender, System.EventArgs e)
{
     using( MyFastORMFramework  myWork = this.CreateFramework())
     {
          RefreshList( myWork );
     }
}

// 用戶名列表當前項 目改變事件處理
private void lstName_SelectedIndexChanged(object sender,  System.EventArgs e)
{
     DB_Employees obj =  lstName.SelectedItem as DB_Employees ;
     if( obj != null )
     {
         this.txtID.Text = obj.EmployeeID.ToString () ;
         this.txtName.Text = obj.FullName ;
          this.txtTitleOfCourtesy.Text = obj.TitleOfCourtesy ;
          this.txtAddress.Text = obj.Address ;
          this.txtNotes.Text = obj.Notes ;
     }
     else
      {
         this.txtID.Text = "";
          this.txtName.Text = "";
         this.txtTitleOfCourtesy.Text =  "";
         this.txtNotes.Text = "" ;
          this.txtAddress.Text = "";
     }
}

// 新增按鈕事件 處理
private void cmdInsert_Click(object sender, System.EventArgs e)
{
     try
     {
         using( dlgRecord  dlg = new dlgRecord())
         {
               dlg.Employe = new DB_Employees();
              if(  dlg.ShowDialog( this ) == DialogResult.OK )
               {
                   using( MyFastORMFramework myWork  = this.CreateFramework())
                   {
                        if( myWork.InsertObject(  dlg.Employe ) > 0 )
                        {
                            RefreshList(  myWork );
                       }
                    }
              }
          }
     }
     catch( Exception ext )
      {
         MessageBox.Show( ext.ToString());
     }
}

// 刪除按鈕事件處理
private void cmdDelete_Click(object  sender, System.EventArgs e)
{
     DB_Employees obj =  this.lstName.SelectedItem as DB_Employees ;
     if( obj != null  )
     {
         if( MessageBox.Show(
               this ,
              "是否刪除 " +  obj.FullName + " 的紀錄?",
              "系統提示" ,
              System.Windows.Forms.MessageBoxButtons.YesNo ) ==  DialogResult.Yes )
         {
               using( MyFastORMFramework myWork = this.CreateFramework())
               {
                   myWork.DeleteObject(  obj );
                   RefreshList( myWork );
              }
         }
     }
}

// 刷新員工名稱列表
private void RefreshList(  MyFastORMFramework  myWork )
{
     object[] objs =  myWork.ReadAllObjects(typeof( DB_Employees ));
      System.Collections.ArrayList list = new ArrayList();
      list.AddRange( objs );
     this.lstName.DataSource = list ;
     this.lstName.DisplayMember = "FullName";
}

// 修改按 鈕事件處理
private void cmdEdit_Click(object sender, System.EventArgs  e)
{
     DB_Employees obj = this.lstName.SelectedItem as  DB_Employees ;
     if( obj == null )
          return ;
     using( dlgRecord dlg = new dlgRecord())
      {
         dlg.txtID.ReadOnly = true ;
          dlg.Employe = obj ;
         if( dlg.ShowDialog( this ) ==  DialogResult.OK )
         {
               using( MyFastORMFramework myWork = this.CreateFramework())
               {
                   if(  myWork.UpdateObject( obj ) > 0 )
                    {
                       RefreshList( myWork  );
                   }
               }
         }
     }
}

 

這段代碼是 比較簡單的,而實體類型DB_Employees的代碼可以很容易的使用代碼生成器生成出來。借助 於快速ORM框架,使得基本的數據庫記錄維護操作開發速度快,運行速度也快。

部署 快速ORM框架

這個快速ORM框架是輕量級的,你只需要將MyFastORMFramework.cs以及 BindTableAttribute和BindFieldAttribute的代碼復制到你的C#工程即可,也可將它們編譯 成一個DLL,供VB.NET等其他非C#的.NET工程使用。

小結

在本課程中,我們使 用反射和動態編譯技術實現了一個快速ORM框架,詳細學習了一個比較簡單的動態編譯技術的 完整實現。動態編譯技術將自由靈活和運行速度結合在一起,是一個比較強大的軟件開發技 術,綜合利用反射和動態編譯技術使得我們有可能打造靈活而高速的程序框架。

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