IDBSession負責與數據庫打交道,它通過ADO.NET實現幾種常用數據庫的連接。目前支持3種數據庫:SQLServer(7.0、2000、2005三個版本,推薦2005)、ORACLE(8.1.7以上,推薦9i)、MSAccess(需要MDAC1.7以上支持)。與IDBSession有關的關係圖如下:
IDBSession提供了跨平臺、參數化的SQL執行,直接支持數據庫內分頁,提供強大的無SQL查詢構造器。在提供簡單、易用、跨平臺功能的同時,並不以損失執行性能爲代價,基於IDBSession編寫數據庫訪問程序性能與直接基於ADO.NET的程序相當。
有三種方式獲取IDBSession:直接構造、通過工廠類構造、通過會話管理器獲取,推薦使用第三種方法,通過會話管理器獲取IDBSession。
方法一:直接構造
public IDBSession CreateSession(string connectionString)
{
return new AccessDBSession(connectionString);
}
方法二:用工廠類構造
IDBSessionFactory factory = new OracleDBSessionFactory(dbVersion, connectionString);
IDBSession session = factory.Create();
或
IDBSession session = factory.Create(newConnectionString);
相比方法一,方法二代碼更容易在不同數據庫上遷移。
方法三:用會話管理器
第一步:配置會話管理器
配置會話管理器有兩種方法,一種是配置DBAccess.config,會話管理器會自動加載DBAccess.config。通常DBAccess.config放在Web應用程序的虛擬目錄的根目錄下或Config子目錄下,或者放在Windows應用程序的啓動目錄或Config子目錄下。具體內容請參考前文“配置DBAccess.config”;另一種方法是用代碼初始化會話管理器:
IDBSessionManager defaultManager = new DBSessionManager();
defaultManager.Add(“IMS”,DBType.Oracle,”9.2”, true,IMSConnectionString);
defaultManager.Add(“ABC”,DBType.SQLServer,”8.0”, true, ABCConnectionString);
第二步:獲取IDBSession
IDBSession session = DBSessionManager.Default.GetSession()
或
IDBSession session = DBSessionManager.Default.GetSession(“IMS”)
方法三比方法二更具有靈活性,由於數據庫連接都配置在配置文件裏,在部署和切換數據庫環境時,只需修改配置文件而不用重新編譯程序。
屬性名
|
類型
|
說明
|
DBType
|
DBType
|
獲取數據庫類型,例如DBType.Oracle、DBType.SQLServer等等
|
State
|
DBSessionState
|
數據庫連接狀態,Open或Close
|
IgnoreCase
|
bool
|
獲取或設置此數據庫在作字符串比較時是否忽略大小寫, 默認爲 true。對於Oracle這類區分大小寫的數據庫,IDBSession將自動用upper(字段名)的方式實現不區分大小寫值比較,這在性能上有一定損耗。
|
IsCaseSensitiveDB
|
bool
|
表明此數據庫是否爲大小寫區分
|
Columns
|
DBColumns
|
獲取最後一次查詢的結果集字段列表
|
AutoGenerateColumns
|
bool
|
獲取或設置查詢時是否自動生成 Columns 屬性,默認爲 true。如果不關注返回的字段列表,可以把此屬性設置爲false以提高批量處理性能。
|
DbConnection
|
DbConnection
|
獲取或設置內部使用的 DbConnection,便於與其他數據庫訪問組件交互調用。
|
DbTransaction
|
DbTransaction
|
獲取或設置內部使用的 DbTransaction,便於與其他數據庫訪問組件交互調用。
|
AllowSubqueryPaging
|
bool
|
獲取或設置是否用子查詢對翻頁進行優化,默認爲 true。例如對於Oracle,使用rownum作分頁查詢。
|
ReadStartIndex
|
int
|
分頁查詢參數,讀取數據的開始行索引號,從0開始,默認爲0。
|
ReadMaxCount
|
int
|
分頁查詢參數,讀取的最大記錄數,默認爲0,表示讀取所有。
|
RecordCount
|
int
|
獲取查詢命中的總記錄數
|
ComputeRecordCount
|
bool
|
獲取或設置是否計算記錄總數,默認爲 false。如果設置爲true,IDBSession 將統計查詢命中的記錄數,會降低查詢性能,合適的做法是僅在分頁查詢打開第一頁時統計一次記錄總數,而後續的頁面瀏覽不再統計記錄總數。
|
ComputeRecordCountOnly
|
bool
|
獲取或設置是否只計算記錄總數,默認爲 false。此屬性可以方便用於統計SQL返回記錄數而不返回結果集。
|
SqlBuilder
|
ISqlBuilder
|
獲取 ISqlBuilder接口。ISqlBuilder接口提供了跨數據庫SQL語句統一構造接口,封裝了常用的數據庫函數,例如字符串連接、TRIM、取字符串子串等。
|
打開和關閉數據庫會話、啓動、提交和回滾數據庫事務的方法如下:
/// <summary>
/// 打開連接,重複執行此操作無效但不出錯
/// </summary>
void Open();
/// <summary>
/// 關閉連接,重複執行此操作無效但不出錯
/// </summary>
void Close();
/// <summary>
/// 開始新事務,重複執行此操作只會增加嵌套級別但不出錯。
/// </summary>
void BeginTran();
/// <summary>
///以 level 事務級別開始新事務,重複執行此操作只會增加嵌套級別但不出錯。
/// </summary>
void BeginTran(IsolationLevel level);
/// <summary>
/// 提交事務,若有事務嵌套,則只在最外層才提交。如果事務尚未打開則拋出 TransactionNotBeginException。
/// </summary>
void CommitTran();
/// <summary>
/// 如果已啓動事務,無論嵌套多少級別都回滾整個事務,重複調用也不出錯。
/// </summary>
void RollbackTran();
相比ADO.NET提供的對應功能,AppFramework實現的更加智能化,例如Open和Close允許重複執行,提高了容錯性;允許事務嵌套,自動判斷嵌套層數,僅在最外層啓動和提交事務,這極大方便了業務類之間的事務組合,不會像ADO.NET多次啓動事務導致異常。
/// <summary>
/// 執行命令,返回Update/Delete/Insert所影響的記錄條數
/// </summary>
/// <param name="cmdText">命令SQL</param>
/// <returns>受影響的記錄數</returns>
int ExecCmd(string cmdText);
/// <summary>
/// 執行查詢,返回結果集的第一行第一列
/// </summary>
/// <param name="cmdText">語句命令</param>
/// <returns>返回Object對象,調用者要自己轉換類型</returns>
object QueryScalar(string cmdText);
/// <summary>
/// 查詢並只返回第一條記錄
/// </summary>
/// <param name="cmdText">命令SQL</param>
/// <returns>字段值數組</returns>
object[] Get(string cmdText);
/// <summary>
/// 查詢並只返回第一條記錄所構造出的對象
/// </summary>
/// <param name="cmdText">命令SQL</param>
/// <param name="create">對象構造器</param>
/// <returns>返回構造的對象</returns>
object Get(string cmdText, ObjectConstructionEventHandler create);
/// <summary>
/// 查詢,返回數據集,不支持分頁
/// </summary>
/// <param name="cmdText">查詢語句</param>
/// <returns>數據集</returns>
DataSet QueryDataSet(string cmdText);
說明:SQLServer數據庫支持一次執行多條查詢語句,放回多個結果集DataSet。其他數據庫例如Oracle的SQL語法不支持此功能。
/// <summary>
/// 查詢,返回數據表,支持分頁
/// </summary>
/// <param name="cmdText">查詢語句</param>
/// <returns>數據表</returns>
DataTable QueryDataTable(string cmdText);
SqlTemplate(SQL模板)實現SQL語句的可配置化,類似IBatis的SqlMapper,支持與IBatis相似的動態SqlMap語法。關於SqlTemplate及其使用,請參考本文後續章節。IDBSession支持通過SqlTemplate作增刪改查,並支持數據庫內翻頁查詢,這一功能比IBatis要實用和強大得多,性能也比IBatis略好。
/// <summary>
/// 執行命令,返回Update/Delete/Insert所影響的記錄條數
/// </summary>
/// <param name="template">SQL模板</param>
/// <param name="parameters">參數</param>
/// <returns>受影響的記錄數</returns>
int ExecCmd(SqlTemplate template, IDictionary<string, object> parameters);
/// <summary>
///執行命令,返回Update/Delete/Insert所影響的記錄條數
/// </summary>
/// <param name="template">SQL模板</param>
/// <param name="parameters">參數</param>
/// <returns>受影響的記錄數</returns>
object QueryScalar(SqlTemplate template, IDictionary<string, object> parameters);
/// <summary>
/// 查詢,返回數據表,支持分頁
/// </summary>
/// <param name="template">SQL模板</param>
/// <param name="parameters">參數</param>
/// <returns>數據表</returns>
DataTable QueryDataTable(SqlTemplate template, IDictionary<string, object> parameters);
SqlTemplate適合於實現複雜的統計查詢,當查詢條件較多、統計邏輯較複雜時,爲了提高代碼的可讀性,可以犧牲一些數據庫移植性或者少量的執行性能,把SQL語句寫在配置文件裏,方便分析和閱讀。
關於SqlTempate,這裏提供幾個使用例子:
SqlTemplate updateUserSqlTemplate = new SqlTemplate(@"<statement id=""UpdateUser"">
update BAS_USER set
Name = #Name#,
En_Name=#En_Name#,
Password=#Password#,
Dept_id=#Dept_id#,
Org_id=#Org_id#,
Employee_NO=#Employee_NO#,
Email=#Email#,
State=#State#,
Creator_ID=#Creator_ID#,
Created_Time=#Created_Time#,
Updated_By=#Updated_By#,
Updated_Time=#Updated_Time#,
Age=#Age#
WHERE id=#ID#
</statement>");
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters[“Name”] =”user_name”;
parameters[“En_Name”] =”user_ en_name”;
……
using (IDBSession session = DBSessionManager.Default.GetSession())
{
int rowCount = session.ExecCmd(updateUserSqlTemplate, parameters);
}
通常SqlTemplate的SQL模板都配置在配置文件裏,通過管理器統一加載。AppFramework的DaoGen文件支持編寫SqlTemplate,代碼生成器將把SqlTemplate生成SqlMap類的派生類,並把相應的參數生成爲類屬性,方便了SQL模板的使用,避免了因文字錯誤或大小寫錯誤導致調用失效。SqlMap使用例子如下:
UpdateUserSqlMap user = new UpdateUserSqlMap();
user.ID = 100;
user.Name = "BatisName";
user.En_Name = "EnBatisName";
user.Created_Time = DateTime.Now;
user.Creator_ID = 0;
user.Dept_id = 100;
user.Email = "Email";
user.Employee_NO = "EmployeeNO";
user.Org_id = 0;
user.Password = "Password";
user.State = 0;
user.Updated_By = 0;
user.Updated_Time = DateTime.Now;
user.Age = 25;
using (IDBSession session = DBSessionManager.Default.GetSession())
{
return session.ExecCmd(user.Template, user.Parameters);
}
以下命令提供一種途徑實現高性能的增刪改查,特別適合代碼生成器使用,但不適合手工編碼人員使用。其中用到了QueryFilter查詢條件構造器,詳細使用方法請參考本文後續章節內容。
/// <summary>
/// 插入一條記錄到指定表中,返回受影響的記錄數
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="fields">字段參數數組</param>
/// <returns>受影響的記錄數</returns>
int Insert(string tableName, params DBField[] fields);
/// <summary>
/// 查詢並只返回第一條記錄[高性能的命令,爲代碼生成器優化]
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="fields">字段名或表達式,“*”表示所有字段</param>
/// <param name="filter">條件</param>
/// <returns>字段值數組</returns>
object[] Get(string tableName, string fields, string filter);
/// <summary>
/// 更新表,返回受影響的記錄數[高性能的命令,爲代碼生成器優化]
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="filter">更新條件</param>
/// <param name="fields">字段參數數組</param>
/// <returns>受影響的記錄數</returns>
int Update(string tableName, string filter, params DBField[] fields);
/// <summary>
/// 刪除表記錄,返回受影響的記錄數[高性能的命令,爲代碼生成器優化]
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="filter">更新條件</param>
/// <returns>返回受影響的記錄數</returns>
int Delete(string tableName, string filter);
/// <summary>
/// 查詢,支持分頁,如果 IDBSesssion的ComputeRecordCountOnly屬性爲 true,則返回null [高性能的命令,爲代碼生成器優化]
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="fields">字段名或表達式,“*”表示所有字段</param>
/// <param name="filter">條件</param>
/// <param name="orderBy">排序表達式</param>
/// <returns>數組,每個元素是一個字段值數組object[]</returns>
IList<object[]> Select(string tableName, string fields, string filter, string orderBy);
/// <summary>
/// 查詢 [高性能的命令,爲代碼生成器優化]
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="fields">字段名或表達式</param>
/// <param name="filter">條件</param>
/// <param name="orderBy">排序表達式</param>
/// <param name="resultList">用來存放對象的鏈表</param>
/// <param name="create">對象構造器</param>
void Select(string tableName, string fields, string filter, string orderBy, IList resultList, BatchObjectConstructionEventHandler create);
QueryFilter功能強大用途廣泛,本後後續章節有詳細說明。SelectStatement類也是基於QueryFilter實現的,因此基於SelectStatement的方法歸根結底也是基於QueryFilter的。與QueryFilter相關的IDBSession方法數量較多,大致羅列如下:
/// <summary>
/// 用查詢結果插入一批記錄到指定表中,返回受影響的記錄數
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="fields">插入表的字段列表,用","間隔</param>
/// <param name="query">對查詢的描述</param>
/// <returns>受影響的記錄數</returns>
int Insert(string tableName, string fields, SelectStatement query);
說明:此Insert方法實現insert…select…子查詢插入語法。SelectStatement代表子查詢,其查詢結果將插入到名爲tableName的表的fields字段裏。
/// <summary>
/// 更新表,返回受影響的記錄數
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="filter">更新條件</param>
/// <param name="fields">字段參數數組</param>
/// <returns>受影響的記錄數</returns>
int Update(string tableName, QueryFilter filter, params DBField[] fields);
說明:此Update方法實現對名爲tableName的表數據的更新,fields表示字段名和字段值,filter表示更新條件。
/// <summary>
/// 刪除表記錄,返回受影響的記錄數
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="filter">更新條件</param>
/// <returns>返回受影響的記錄數</returns>
int Delete(string tableName, QueryFilter filter);
/// <summary>
/// 執行查詢,返回結果集的第一行第一列
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="field">字段名或表達式,“*”表示所有字段</param>
/// <param name="filter">條件</param>
/// <returns>返回Object對象,調用者要自己轉換類型</returns>
object QueryScalar(string tableName, string field, QueryFilter filter);
/// <summary>
/// 執行查詢,返回結果集的第一行第一列
/// </summary>
/// <param name="query">查詢對象</param>
/// <returns>返回Object對象,調用者要自己轉換類型</returns>
object QueryScalar(SelectStatement query);
/// <summary>
/// 查詢並只返回第一條記錄
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="fields">字段名或表達式,“*”表示所有字段</param>
/// <param name="filter">條件</param>
/// <returns>字段值數組</returns>
object[] Get(string tableName, string fields, QueryFilter filter);
說明:此Get方法對名爲tableName的表進行查詢,但只返回第一條數據,fields表示要求返回的字段,filter表示查詢條件,返回的object[]數組存放着查詢到的記錄的字段值,字段順序與fields的字段書寫順序一致,如果 fields爲*,則表示返回所有字段,字段順序由表字段順序和數據庫決定。
因爲涉及到字段順序,此方法適合代碼生成器使用。
/// <summary>
/// 查詢並只返回第一條記錄
/// </summary>
/// <param name="query">查詢對象</param>
/// <returns>字段值數組</returns>
object[] Get(SelectStatement query);
說明:因爲涉及到字段順序,此方法適合代碼生成器使用。
/// <summary>
/// 查詢,返回字段數組鏈表,支持分頁,如果 IDBSesssion的ComputeRecordCountOnly屬性爲 true,則返回null
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="fields">字段名或表達式,“*”表示所有字段</param>
/// <param name="filter">條件</param>
/// <param name="orderBy">排序表達式</param>
/// <returns>數組,每個元素是一個字段值數組object[]</returns>
IList<object[]> Select(string tableName, string fields, QueryFilter filter, string orderBy);
說明:因爲涉及到字段順序,此方法適合代碼生成器使用。
/// <summary>
/// 查詢,支持分頁,返回的對象放入resultList鏈表
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="fields">字段名或表達式</param>
/// <param name="filter">條件</param>
/// <param name="orderBy">排序表達式</param>
/// <param name="resultList">用來存放對象的鏈表</param>
/// <param name="create">對象構造器</param>
void Select(string tableName, string fields, QueryFilter filter, string orderBy, IList resultList, BatchObjectConstructionEventHandler create);
說明:Select方法實現對錶的查詢,同時通過create委託爲每條記錄生成對象,並把對象放入resultList鏈表中。此方法性能比基於DataTable的查詢好得多,測試數據表明,前者比後者性能高30%以上。相比ObjectConstructionEventHandler,BatchObjectConstructionEventHandler 是專爲批量創建對象定製的委託,它允許把一些對象創建所需的中間結果保存到委託事件參數裏,再讓後續的對象創建過程使用,直接提高了批量創建對象的性能。
/// <summary>
/// 查詢,支持分頁,如果 IDBSesssion的ComputeRecordCountOnly屬性爲 true,則返回null
/// </summary>
/// <param name="query">查詢對象</param>
/// <returns>數組,每個元素是一個字段值數組object[]</returns>
IList<object[]> Select(SelectStatement query);
說明:因爲涉及到字段順序,此方法適合代碼生成器使用。
/// <summary>
/// 查詢,支持分頁,返回的對象放入resultList鏈表
/// </summary>
/// <param name="query">查詢對象</param>
/// <param name="resultList">用來存放對象的鏈表</param>
/// <param name="create">對象構造器</param>
void Select(SelectStatement query, IList resultList, BatchObjectConstructionEventHandler create);
/// <summary>
/// 查詢並只返回第一條記錄
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="fields">字段名或表達式</param>
/// <param name="filter">條件</param>
/// <param name="create">對象構造器</param>
/// <returns>返回構造的對象</returns>
object Get(string tableName, string fields, QueryFilter filter, ObjectConstructionEventHandler create);
/// <summary>
/// 查詢並只返回第一條記錄
/// </summary>
/// <param name="query">查詢對象</param>
/// <param name="create">對象構造器</param>
/// <returns>返回構造的對象</returns>
object Get(SelectStatement query, ObjectConstructionEventHandler create);
/// <summary>
/// 查詢,返回數據表,支持分頁,如果 IDBSesssion的ComputeRecordCountOnly屬性爲 true,則返回null
/// </summary>
/// <param name="tableName">指定要填充的數據表名</param>
/// <param name="fields">要查詢的字段名,“*”表示所有字段</param>
/// <param name="filter">查詢條件</param>
/// <param name="orderBy">排序表達式,如:"xxxx asc, xxxx1 desc"</param>
/// <returns>數據表</returns>
DataTable QueryDataTable(string tableName, string fields, QueryFilter filter, string orderBy);
/// <summary>
/// 查詢,返回數據表,支持分頁,如果 IDBSesssion的ComputeRecordCountOnly屬性爲 true,則返回null
/// </summary>
/// <param name="query">查詢對象</param>
/// <returns>數據表</returns>
DataTable QueryDataTable(SelectStatement query);
此代理的目的是爲了提供一種優化途徑,便於開發者最大程度地提升IDBSession構造實體對象的速度。事件包含兩個參數:
1、DBColumns columns:從數據庫裏讀取到的列的信息;
2、object[] values:從數據庫裏讀取到的數據行信息;
此代理的目的與ObjectConstructionEventHandler相似,後者僅用於返回單行數據的查詢,而BatchObjectConstructionEventHandler主要用於返回多行數據的查詢,便於開發者最大程度地提升在一個查詢構造多個實體對象的速度。
事件參數BatchObjectConstructionEventArgs包含如下信息:
1、DBColumns columns:從數據庫裏讀取到的列的信息;
2、object[] values:從數據庫裏讀取到的數據行信息;
3、object Context:供事件處理器存放上下文信息,這一信息會被保留下來,在下一次調用時傳給事件處理器。開發者可以充分利用Context屬性,保存一些可以多次利用的信息,減少重複計算進而提高執行速度。
大部分IDBSession的查詢方法都支持分頁查詢。分頁查詢有幾個要點,第一、設置是否統計查詢結果記錄總數;第二、設置查詢返回記錄開始索引;第三、設置查詢返回記錄數。由於統計查詢結果記錄總數非常耗時,應該只在查詢時才統計,翻頁時不應統計。推薦的代碼模板如下:
bool query =false;
private void btnQuery_Click(object sender, EventArgs e)
{
query = true;//只有點擊“查詢”按鈕時才統計記錄數
dataGrid.CurrentPageIndex = 0;
Query();
}
private void dataGrid_PageIndexChanged(object sender, DataGridPageChangedEventArgs e)
{
query = false;
dataGrid.CurrentPageIndex = e.NewPageIndex;
Query();
}
private void Query()
{
using (IDBSession session = DBSessionManager.Default.GetSession())
{
session.ComputeRecordCount = query; //控制只在第一次查詢或刷新時才統計記錄數
session.ReadStartIndex = dataGrid.CurrentPageIndex *pageSize; //從當前頁第一條記錄讀起
session.ReadMaxCount = pageSize;//只讀取一頁數據
DataTable dt = session.QueryDataTable(“select * from BAS_USER”);
dataGrid.DataSource = dt;
dataGrid.DataBind();
if (query) //如果統計了記錄總數,則顯示記錄總數
{
t xtRecordCount.Text = session.RecordCount;
}
}
}
有一種更加優化的分頁策略是“分塊查詢”,例如每頁20條,每10頁爲1塊,一次性讀取1塊記錄(合200條)記錄存到內存中,在翻頁時檢查頁碼是否在塊內,如果在塊內則直接從內存中取出數據綁定到DataGrid;如果不在塊內,則從數據庫裏讀取下一塊數據並顯示指定的頁碼。
AppFramework.UI.WebDataGrid的組件提供了非常方便的分塊查詢翻頁功能,歡迎瞭解使用。