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

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

編輯:關於C#

在InnerReadValue函數中遍歷所有的屬性綁定信息,調用IDataReader.GetValue函數從數據庫中獲得原始數據,然後調用FieldBindInfo.FromDataBase函數對這個原始數據進行一些處理,主要是進行格式化和數據類型轉換,然後調用PropertyInfo.SetValue函數根據讀取的數據設置對象實例的屬性值。這樣遍歷了所有的綁定信息也就完整的填充了對象實例的屬性值。

在ReadObjects函數中,遍歷所有查詢的數據,對每個記錄創建一個對象實例,遍歷數據庫記錄完畢後,我們就將所有創建的對象實例組成一個數組作為函數返回值,然後退出函數。

我們可以在這個ReadObjects函數上面派生出一系列的從數據庫讀取對象的函數。這個ReadObjects函數就實現了框架程序讀取數據這個核心功能之一。

此外我們還定義了一個Contains函數用於判斷一個應用程序對象實例對應的數據庫記錄是否存在。

新增數據

框架程序的InsertObjects函數就能將若干個對象插入的數據庫表中。其主要代碼為

/// <summary>
/// 將若干個對象插入到數據庫中
/// </summary>
/// <param name="Objects">對象列表</param>
/// <param name="TableName">制定的數據表,若未指定則使用默認的數據表名</param>
/// <returns>插入的數據庫記錄的個數</returns>
public int InsertObjects( System.Collections.IEnumerable Objects , string TableName )
{
if( Objects == null )
{
throw new ArgumentNullException("Objects");
}
this.CheckBindInfo( Objects , false );
System.Collections.ArrayList list = new System.Collections.ArrayList();
foreach( object obj in Objects )
{
list.Add( obj );
}
if( list.Count == 0 ) {
return 0 ;
}
this.CheckConnetion();
// 上一次執行的SQL語句
string strLastSQL = null ;
int InsertCount = 0 ;
using( System.Data.IDbCommand cmd = myConnection.CreateCommand())
{
foreach( object obj in list )
{
TableBindInfo table = this.GetBindInfo( obj.GetType());
string TableName2 = TableName ;
if( TableName2 == null || TableName.Trim().Length == 0 )
{
TableName2 = table.TableName ;
}
System.Collections.ArrayList values = new System.Collections.ArrayList();
// 拼湊SQL語句
System.Text.StringBuilder myStr = new System.Text.StringBuilder();
System.Text.StringBuilder myFields = new System.Text.StringBuilder();
foreach( FieldBindInfo field in table.Fields )
{
if( field.Property.CanRead == false )
{
throw new Exception("屬性 " + field.Property.Name + " 是不可寫的");
}
object v = field.Property.GetValue( obj , null );
if( v == null || DBNull.Value.Equals( v ))
{
continue ;
}
values.Add( field.ToDataBase( v ));
if( myStr.Length > 0 )
{
myStr.Append(" , ");
myFields.Append( " , " );
}
myStr.Append(" ? " );
myFields.Append( FixFieldName( field.FieldName ));
}//foreach
myStr.Insert( 0 , "Insert Into " + FixTableName( TableName2 )
+ " ( " + myFields.ToString() + " ) Values ( " );
myStr.Append( " ) " );
string strSQL = myStr.ToString();
if( strSQL != strLastSQL )
{
// 重新設置SQL命令對象
strLastSQL = strSQL ;
cmd.Parameters.Clear();
cmd.CommandText = strSQL ;
for( int iCount = 0 ; iCount < values.Count ; iCount ++ )
{
cmd.Parameters.Add( cmd.CreateParameter());
}
}
// 填充SQL命令參數值
for( int iCount = 0 ; iCount < values.Count ; iCount ++ )
{
( ( System.Data.IDbDataParameter ) cmd.Parameters[ iCount ]).Value = values[ iCount ] ;
}
// 執行SQL命令向數據表新增記錄
InsertCount += cmd.ExecuteNonQuery();
}//foreach
}//using
return InsertCount ;
}

在這個函數的參數是對象列表和要插入的數據庫表名稱。在函數中首先是遍歷應用程序對象列表,對每一個對象調用GetBindInfo函數獲得綁定信息,然後遍歷所有對象類型屬性綁定信息,拼湊出一個“Insert Into TableName ( 字段1,字段2 … ) Values ( 屬性值1, 屬性值2 … ) ”這樣的SQL語句,這裡使用了PropertyInfo.GetValue函數來從對象實例中獲得指定的屬性值,我們並沒有將屬性值直接放入到SQL語句中,而是采用了SQL參數的方式來存放屬性值。

SQL語句拼湊完畢後我們就設置SQL命令對象,然後執行它,這樣就向數據庫插入一條數據庫記錄了。這裡我們還使用了一個strLastSQL的變量來保存上次執行的SQL語句,這樣可以減少設置SQL命令對象的次數,提高性能。

向數據庫插入所有的對象後,我們就累計所有插入的數據庫記錄的個數為函數返回值,然後退出函數。

這個函數其過程也不復雜,我們在這個函數上面派生了一系列的向數據庫插入記錄的方法以豐富ORM框架的接口。

修改數據

框架定義了一個UpdateObjects函數用於根據應用程序對象來修改數據庫記錄,其主要代碼為

/// <summary>
/// 更新多個對象
/// </summary>
/// <param name="Objects">對象列表</param>
/// <returns>更新修改的數據庫記錄個數</returns>
public int UpdateObjects( System.Collections.IEnumerable Objects )
{
if( Objects == null )
{
throw new ArgumentNullException("Objects");
}
this.CheckBindInfo( Objects , true );
this.CheckConnetion();
int RecordCount = 0 ;
using( System.Data.IDbCommand cmd = myConnection.CreateCommand())
{
foreach( object obj in Objects ) {
TableBindInfo table = this.GetBindInfo( obj.GetType());
// 拼湊生成SQL更新語句
System.Collections.ArrayList values = new System.Collections.ArrayList();
System.Text.StringBuilder myStr = new System.Text.StringBuilder();
foreach( FieldBindInfo field in table.Fields )
{
object v = field.Property.GetValue( obj , null );
if( myStr.Length > 0 )
{
myStr.Append(" , " + System.Environment.NewLine );
}
myStr.Append( FixFieldName( field.FieldName ) + " = ? " );
values.Add( field.ToDataBase( v ));
}
myStr.Insert( 0 , "Update " + FixTableName( table.TableName ) + " Set " );
string strSQL = BuildCondition( obj , values );
myStr.Append( " Where " + strSQL );
strSQL = myStr.ToString();
// 設置SQL命令對象,填充參數
cmd.Parameters.Clear();
cmd.CommandText = strSQL ;
foreach( object v in values )
{
System.Data.IDbDataParameter p = cmd.CreateParameter();
cmd.Parameters.Add( p );
p.Value = v ;
}
RecordCount += cmd.ExecuteNonQuery();
}//foreach
}//using
return RecordCount ;
}
/// <summary>
/// 根據對象數值創建查詢條件子SQL語句
/// </summary>
/// <param name="obj">對象</param>
/// <param name="values">SQL參數值列表</param>
/// <returns>創建的SQL語句字符串</returns>
private string BuildCondition( object obj , System.Collections.ArrayList values )
{
TableBindInfo table = this.GetBindInfo( obj.GetType() );
// 拼湊查詢條件SQL語句
System.Text.StringBuilder mySQL = new System.Text.StringBuilder();
foreach( FieldBindInfo field in table.Fields )
{
if( field.Attribute.Key )
{
object v = field.Property.GetValue( obj , null );
if( v == null || DBNull.Value.Equals( v ))
{
throw new Exception("關鍵字段屬性 " + field.Property.Name + " 未指定值" ) ;
}
if( mySQL.Length > 0 )
{
mySQL.Append(" And " );
}
mySQL.Append( FixFieldName( field.FieldName ));
mySQL.Append( " = ? " );
values.Add( field.ToDataBase( v ));
}
}//foreach
if( mySQL.Length == 0 )
{
throw new Exception("類型 " + obj.GetType().FullName + " 未能生成查詢條件");
}
return mySQL.ToString();

}

這個函數的參數是應用程序對象實例列表,在函數中遍歷這個列表,對於每一個對象實例調用GetBindInfo函數獲得綁定信息,然後遍歷所有的對象屬性的綁定信息,這樣就可以拼湊出一個“Update Table Set 字段名1=屬性值1 , 字段名2=屬性值2 , 字段名3=屬性值3…”這樣的SQL語句。

我們還調用BindCondition函數來創建該SQL語句的Where子語句用於設置更新數據庫記錄的查詢條件。在BindCondition函數中,遍歷查找所有標記為關鍵字的屬性綁定信息,然後拼湊出一個“字段名1=屬性值1 and 字段名2=屬性值2 …”這樣的SQL語句,並調用PropertyInfo.GetValue函數來獲得關鍵字段屬性值。

這裡和InsertObjects函數類似,我們並沒有將對象實例的屬性值嵌入在SQL語句中,而是使用SQL命令參數的方式來保存對象實例的屬性值。

完整的用於更新數據庫記錄的SQL語句拼湊完畢後,我們就設置SQL命令對象,然後執行SQL語句,這樣就能根據對象來修改數據庫的記錄,然後我們設置累計的修改數據庫記錄的個數作為返回值後退出函數。

我們還在UpdateObjects的基礎上派生了一些其他函數用於豐富ORM框架的編程接口。

刪除數據

ORM框架定義了一個DeleteObjects函數用於刪除數據庫記錄,其主要代碼為

/// <summary>
/// 刪除若干條對象的數據
/// </summary>
/// <param name="Objects">對象列表</param>
/// <returns>刪除的記錄個數</returns>
public int DeleteObjects( System.Collections.IEnumerable Objects )
{
if( Objects == null )
{
throw new ArgumentNullException("Objects");
}
this.CheckBindInfo( Objects , true );
this.CheckConnetion();
int RecordCount = 0 ;
using( System.Data.IDbCommand cmd = myConnection.CreateCommand())
{
foreach( object obj in Objects )
{
TableBindInfo table = this.GetBindInfo( obj.GetType() );
// 拼湊SQL語句
System.Collections.ArrayList values = new System.Collections.ArrayList();
string strSQL = BuildCondition( obj , values );
strSQL = "Delete From " + FixTableName( table.TableName ) + " Where " + strSQL ;
// 設置SQL命令對象
cmd.Parameters.Clear();
cmd.CommandText = strSQL ;
foreach( object v in values )
{
System.Data.IDbDataParameter p = cmd.CreateParameter();
p.Value = v ;
cmd.Parameters.Add( p );
}
// 執行SQL,刪除記錄
RecordCount += cmd.ExecuteNonQuery();
}
}
return RecordCount ;
}

該函數的參數是應用系統對象實例的列表。在這個函數中遍歷所有的對象實例,調用GetBindInfo函數獲得映射信息,然後拼湊出一個”Delete From 映射的數據表名 Where 查詢條件”的SQL語句,這裡使用BindCondition函數來創建Where子語句,然後使用拼湊的SQL語句設置SQL命令對象並執行,這樣就從數據庫中刪除了應用程序對象對應的數據庫記錄了。

我們在DeleteObjects的基礎上派生了一些函數用於豐富框架的編程接口。

使用ORM框架

ORM框架開發完畢後,我們就來簡單的測試這個框架,首先我們在一個Access2000的數據庫中建立一個名為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;
}
}
///<summary>
/// 字段值 FirstName
///</summary>
private System.String m_FirstName = null ;
///<summary>
/// 字段值 FirstName
///</summary>
[BindField("FirstName")]
public System.String FirstName
{
get
{
return m_FirstName ;
}
set
{
m_FirstName = value;
}
}
其他字段……………..
#endregion
}// 數據庫操作類 DB_Employees 定義結束

這個類型的代碼很簡單,就是定義一個個和數據庫字段對應的公開屬性而已,因此很容易使用各種代碼生成器來生成這樣的代碼。這個DB_Employees類型中使用了BindTable和BindField特性來標記對象及其屬性綁定了數據庫表和字段上,這裡的EmployeeID屬性標記為關鍵字段,因此框架程序修改和刪除數據庫記錄是會依據EmployeeID來生成查詢條件。

我們開發了一個簡單的WinForm程序來測試我們建立的ORM框架。其用戶界面為

查詢數據

用戶界面上的“刷新”按鈕就是讀取數據庫,然後刷新員工名稱列表,其主要代碼為

private void cmdRefresh_Click(object sender, System.EventArgs e)
{
using( MyORMFramework myWork = this.CreateFramework())
{
RefreshList( myWork );
}
}
private void RefreshList( MyORMFramework 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";
}
/// <summary>
/// 連接數據庫,創建ORM框架對象
/// </summary>
/// <returns>ORM框架對象</returns>
private MyORMFramework 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 MyORMFramework( conn );
}

該按鈕的點擊事件處理中,首先調用CreateFramework函數連接數據庫,創建一個ORM框架對象的實例,然後調用RefreshList函數來刷新列表。

RefreshList函數中,首先調用ORM框架的ReadAllObjects函數獲得數據庫中所有的類型為DB_Employees的對象,ReadAllObjects函數內部調用了ReadObjects函數。程序獲得一個對象數組後放置到一個ArrayList中,然後設置列表框控件的數據源和顯示字段名稱,這樣就刷新了員工名稱列表框的內容了。

用戶點擊員工名稱列表框的某個姓名後就會在用戶界面的右邊顯示該員工的詳細信息,其處理過程的代碼為

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 = "";
}
}

這個過程也很簡單,用戶刷新員工列表框後,該列表框的列表內容都是DB_Employees類型,我們就獲得當前的員工信息對象,然後一個個設置右邊的文本框的內容為各個屬性值就可以了。

新增數據

我們點擊“新增”按鈕就會向數據庫新增一條記錄,其主要代碼為

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( MyORMFramework myWork = this.CreateFramework())
{
if( myWork.InsertObject( dlg.Employe ) > 0 )
{
RefreshList( myWork );
}
}
}
}
}
catch( Exception ext )
{
MessageBox.Show( ext.ToString());
}
}

該函數中首先調用員工信息編輯對話框來輸入新增員工的信息,該對話框的用戶界面為

用戶確 認輸入新增員工的信息後,程序調用CreateFramework的函數創建一個ORM框架對象的實例, 然後調用它的InsertObject函數來向對象插入一個數據庫記錄,InsertObject函數內部會調 用上面介紹的InsertObjects函數。如果插入的數據庫記錄個數大於0則調用RefreshList函數 來刷新左邊的員工列表。

修改數據

用戶點擊“修改”按鈕後就能 修改當前員工數據並修改數據庫記錄。其主要代碼為

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( MyORMFramework myWork  = this.CreateFramework())
               {
                    if( myWork.UpdateObject( obj ) > 0 )
                    {
                        RefreshList( myWork );
                    }
              }
          }
      }
}   

在這個按鈕點擊事件處理中,首先調用員工信息編輯對話框來編輯當前員 工的信息,當用戶修改並確認後,程序創建一個ORM框架對象實例,然後調用UpdateObject函 數來修改數據庫記錄,UpdateObject函數內部調用上面介紹的UpdateObjects函數。若成功的 修改數據庫記錄則調用RefreshList函數來更新列表。

刪除數據

用戶點擊 “刪除”按鈕來刪除數據庫記錄,其主要代碼為

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( MyORMFramework myWork  = this.CreateFramework())
          {
                    myWork.DeleteObject( obj );
                    RefreshList( myWork );
               }
          }
      }
} 

在這個按鈕點擊事件處理中,程序 首先讓用戶確認刪除操作,然後創建一個ORM框架對象,然後調用它的DeleteObject函數來刪 除對象對應的數據庫記錄,然後調用RefreshList函數來刷新列表。

從這個例子可以 看出,使用ORM框架,對於最常見的查詢,新增,修改和刪除數據庫記錄的操作將變得比較簡 單,而且和數據庫表對應的對象類型的代碼很簡單,可以很容易的采用代碼生成器來生成它 們的代碼,以後若數據庫結構發生改變,只需更新這些數據表對應的實體類的代碼就可以了 。這些特性都能比較大的降低開發和維護成本,提高開發速度。

部署ORM框架

由於這個ORM框架是輕量級的,不依賴任何其他非標准組件,因此部署非常方便,我們可以將 修改這個演示程序工程樣式為DLL樣式,編譯生成一個DLL即可投入使用,也可以將代碼文件 MyORMFramework.cs或者其內容復制粘貼到你的C#工程中即可。

小結

在本課程 中,我們使用了.NET框架提供的反射和特性來構造了一個簡單的ORM框架。反射就是.NET程序 的基因分析技術,功能強大,使用也不復雜。特性本身不影響程序的運行,但能對各種軟件 編程單元進行標記,可以指引某些程序模塊的運行。反射和特性都是C#的一種比較高級的編 程技巧,好好利用可以構造出非常靈活的程序框架。

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