AppFramework1.0數據庫訪問組件使用說明(五)IDBSession使用詳解

 
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。其他數據庫例如OracleSQL語法不支持此功能。
 
/// <summary>
/// 查詢,返回數據表,支持分頁
/// </summary>
/// <param name="cmdText">查詢語句</param>
/// <returns>數據表</returns>
DataTable QueryDataTable(string cmdText);
1.1.5  以SqlTemplate爲參數的命令
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>
/// 查詢,支持分頁,如果 IDBSesssionComputeRecordCountOnly屬性爲 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>
/// 查詢,返回字段數組鏈表,支持分頁,如果 IDBSesssionComputeRecordCountOnly屬性爲 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%以上。相比ObjectConstructionEventHandlerBatchObjectConstructionEventHandler 是專爲批量創建對象定製的委託,它允許把一些對象創建所需的中間結果保存到委託事件參數裏,再讓後續的對象創建過程使用,直接提高了批量創建對象的性能。
 
/// <summary>
/// 查詢,支持分頁,如果 IDBSesssionComputeRecordCountOnly屬性爲 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>
/// 查詢,返回數據表,支持分頁,如果 IDBSesssionComputeRecordCountOnly屬性爲 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>
/// 查詢,返回數據表,支持分頁,如果 IDBSesssionComputeRecordCountOnly屬性爲 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的組件提供了非常方便的分塊查詢翻頁功能,歡迎瞭解使用。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章