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);
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>
///
查询,支持分页,如果
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;
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的组件提供了非常方便的分块查询翻页功能,欢迎了解使用。