微软企业库DBBA的研究
Summary:
如何入门使用Data Access Application Block,可以参考Enterprise Library 3.1中文帮助:数据访问应用程序块。这篇文章侧重在DAAB工作原理、代码结构上的一些注解。
DAAB中抽象类Database提供了针对数据库操作的接口,从它派生出来的各个数据库类使用工厂方式创建。我们可以基于这个机制进行扩展,实现对其它数据库的支持,例如:Ent Lib 2.0 DAAB添加MySql扩展 MySql5.0.27+MySql .Net Connector 5.0.2beta。
DAAB
希望使用通用接口对不同的数据库进行操作。DAAB的创建机制封装了不同数据库Provider的创建过程;Database类提供的接口是基于
ADO.NET进行数据访问操作的一种指导。还有另外一个重要方面,例如NHibernate内部使用Dialect来封装数据库之间的差异(参数标识
符、对象引用的开/闭符号、各种数据库函数的封装、语法差异等),这些扩展与Database类以及配置上总是需要一些结合点。
另外,参考写有效率的SQL查询(IV),在这里就有实际遇到Oracle.DataAccess 10.2的问题?,
这都是执行SQL时参数类型信息给的不全,而导致数据库索引失效而带来性能问题(还有其它问题的,例如Oracle中的Clob、NClob,如果更新这
些字段的时候没有指定参数为Clob、NClob,则存入的包含中文的数据有时会变成乱码,有时又是好的)。cnblogs中也有几篇文章提到参数信息不
完全导致执行计划不能被重用的情况优化SQL Server的内存占用之执行缓存,详细的执行计划缓存参考SQL Server 2005 中的批编译、重新编译和计划缓存问题。
另外看过MySql.Data.dll、System.Data.OracleClient.dll、Oracle.DataAccess.dll等从
DbType枚举向相应数据库枚举,例如MySqlDbType、OracleDbType等转换时,很多时候可能不是你想要的结果,因此我们可能希望定
义自己的标准类型,按照自己的需求向各个数据库类型转换。这也要求能够跟当前使用的数据库类型具有一个结合点。
上面这些是使用DAAB比较常见的扩展需求,这要求对DAAB内部处理方式比较熟悉。
OverView:
DAAB整体结构如下图:
结合使用方式来看Database类的主要工作。
ExecuteDataSet, ExecuteNonQuery, ExcecuteReader, ExecuteScalar
Database database = DatabaseFactory.CreateDatabase();
DbCommand command = database.GetSqlStringCommand("select * from cms_box");
DataSet ds = database.ExecuteDataSet(command);
string sql = "select * from cms_box where box_name like " + database.BuildParameterName("boxName");
command = database.GetSqlStringCommand(sql);
database.AddParameter(command, "boxName", DbType.AnsiString, 30, ParameterDirection.Input
, true, 0, 0, "", DataRowVersion.Default, "%Box002%");
ds = database.ExecuteDataSet(command);
都有多个重载版本,主要可以分为2类,以参数中是否包含IDbTransaction划分。对于不使用Transaction参数的版
本,DAAB每次新建一个Connection用于查询,自动Open和Close
Connection对象;使用Transaction参数的版本,每次从Transaction中获取连接对象,而不是新建。
Database
的内嵌类ConnectionWrapper,主要目的是对TransactionScope的支持。如果有使用
TransactionScope,DAAB以配置文件(后面讲述)中每个Database的connection
string作为key值,将当前TransactionScope中用到的所有Connection对象缓存起来。这样同一个
TransactionScope中执行的每个操作,如果是同一个Database(指connection
string相同,就算不是同一个实例),都使用一个Connection对象来执行;不同的Database使用不同的Connection对象。在执
行TransactionScope.Complete()的时候DAAB会自动关闭释放当前TransactionScope的所有连接对象。
这个主要是Database内嵌类ConnectionWrapper和TransactionScopeConnections完成。
Stored Procedure
DataSet ds = db.ExecuteDataSet("SP_QueryUser"
, new object[] { "%Kevin%", Convert.ToDateTime("2007-10-20") });
DbCommand cmd = db.GetStoredProcCommand("SP_QueryUser"
, new object[] { "%Kevin%", Convert.ToDateTime("2007-10-20") });
ds = db.ExecuteDataSet(cmd);
执行存储过程时不需要提供参数列表(包括指定参数类型、长度等),DAAB使用ADO.NET中各种数据库Provider的
xxxCommandBuilder.DeriveParameters()方法获取存储过程参数信息。DAAB内部在每个存储过程第一次使用时建一个新
的连接调用DeriveParameters()获取参数,并将参数缓存起来,以后调用时从缓存中取参数信息。
完成这个处理的类是ParameterCache、CachingMechanism以及Database,各个派生自Database的具体类必须实现DeriveParameters方法。
DataSet Update
DataSet ds = db.ExecuteDataSet(CommandType.Text, "select * from cms_box");
foreach (DataRow row in ds.Tables[0].Rows)
{
row["SKIN_CODE"] = "BlueSky";
//other code that changes the dataset
}
//the following code will cause a row update exception
//because the text length is greater than the length of the database field
ds.Tables[0].Rows[1]["SKIN_CODE"] = "text more than 10 chars";
update command DbCommand updateCmd = db.GetSqlStringCommand(
string.Format("update cms_box set SKIN_CODE={0} where BOX_ID={1}"
, db.BuildParameterName("skinCode")
, db.BuildParameterName("boxId")));
db.AddParameter(updateCmd, "skinCode", DbType.AnsiString, ParameterDirection.Input
, "SKIN_CODE", DataRowVersion.Current, null);
db.AddParameter(updateCmd, "boxId", DbType.Int32, ParameterDirection.Input
, "BOX_ID", DataRowVersion.Current, null);
//create other commands if needed
db.UpdateDataSet(ds, ds.Tables[0].TableName, null, updateCmd, null, UpdateBehavior.Continue);
获取一个DataSet,对它编辑,然后可以调用Database.UpdateDataSet()批量更新。olumn和DataRowVersion属性。
UpdateDataSet
方法可以指定批量大小,即每次提交的SQL中包含多少个更新语句。DAAB使用ADO.NET的DataAdapter进行批量更新,需要确认数据库
Provider的DataAdapter实现了UpdateBatchSize方式,例如MySql.Data.dll
5.0.7版本就没有实现。DAAB没有为具体子类提供影响抽象基类Database UpdateDataSet()处理行为的机会,所以如果在SQL
Server数据库上使用DataSet
Update并使用BatchSize,这样的代码无法平滑过渡到MySql等不支持BatchSize的数据库。
UpdateBehavior枚举用来控制批量更新过程中,个别更新异常时的行为,有三种方式:
1. Standard。发生异常将终止更新过程,对于数据库使用隐式事务的情况,结果是部分更新部分未更新。
2. Continue。某个记录更新异常时继续更新随后的纪录。要支持这种方式,继承自Database的具体类必须重写SetUpRowUpdatedEvent()方法,在该方法中设置更新记录异常处理事件。
3. Transactional。对批量更新过程使用事务控制,要么全部成功,要么全部失败。如果指定了这种方式,使用的是不带DbTransaction参数的UpdateDataSet()方法,则DAAB在内部开始一个事物,用来完成这次批量更新操作。
Configuration
下面的配置示例都是写在应用程序配置文件中的,例如web.config或app.config。
1. DAAB已经提供了SqlDatabase、OracleDatabase、GenericDatabase,使用SqlDatabase、OracleDatabase的配置文件如下:
<section name="dataConfiguration"
type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data" />
<!--, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a-->
</configSections>
<dataConfiguration defaultDatabase="localServer"></dataConfiguration>
<connectionStrings>
<add name="localServer" connectionString="..." providerName="..." />
</connectionStrings>
其中,connectionStrings节点下的providerName为System.Data.SqlClient或者System.Data.OracleClient。
connectionStrings
节点下面每一个add节点代表一个数据库配置,DatabaseFactory.CreateDatabase(string
name)的name参数就是这个节点的name属性。不使用参数创建Database将使用dataConfiguration节点
defaultDatabase属性指定的默认数据库。
2. 假如从Database继承,派生了自己的Database类,例如Ent Lib 2.0 DAAB添加MySql扩展 MySql5.0.27+MySql .Net Connector 5.0.2beta中的MySqlDatabase,如下配置:
<section name="dataConfiguration"
type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data" />
<!--, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a-->
</configSections>
<dataConfiguration defaultDatabase="localServer">
<providerMappings>
<add name="MySqlDatabase" databaseType="TS.DAL.MySqlDatabase, TS.DAL" />
</providerMappings>
</dataConfiguration>
<connectionStrings>
<add name="localServer" connectionString="..." providerName="MySqlDatabase" />
</connectionStrings>
connectionStrings节点中的previderName指向providerMappings中provider的配置。
3.
其它情况下,将使用GenericDatabase这个类,例如你可以使用OleDb、ODBC驱动。这种情况配置跟1完全一样,可以选择的
providerName可以从machine.config的DbProviderFactories中找到,使用invariant属性值,例如
System.Data.OleDb、System.Data.Odbc等。
如果数据库的Provider并没有注册到
machine.config的DbProviderFactories节点中,则可以使用下面的方式在应用程序配置文件中指定。假设使用MySql的
Provider,没有安装MySQL Connector for
Net(不会在machine.config的DbProviderFactories中注册),而只是把MySql.Data.dll文件拷贝到应用程
序运行目录(并且假设没有从Database派生自己的MySqlDatabase类,而直接使用GenericDatabase):
<section name="dataConfiguration"
type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data" />
<!--, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a-->
</configSections>
<dataConfiguration defaultDatabase="localServer"></dataConfiguration>
<connectionStrings>
<add name="localServer" connectionString="..." providerName="MySql.Data" />
</connectionStrings>
<system.data>
<DbProviderFactories>
<add name="MySql Data Provider" invariant="MySql.Data" description="MySql Data Provider"
type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data" />
</DbProviderFactories>
</system.data>
使用GenericDatabase时有些功能不能使用,例如调用存储过程时自动获取参数、DataSet Update不能使用Continue方式、参数标识符为空等,因为GenericDatabase中没有实现这些功能。
DAAB
通过EnterpriseLibrary
Common库使用.Net标准方式读取配置文件信息,默认情况下,DatabaseFactory从应用程序配置文件(web.config或
app.config)中读取,我们也可以改变这种方式,自定义一个DatabaseFactory(非常简单),从另外的文件中读取配置信息:
{
FileConfigurationSource config = new FileConfigurationSource(@"c:"database.config");
DatabaseProviderFactory provider = new DatabaseProviderFactory(config);
return provider.CreateDefault();
}
最主要的配置节点为connectionStrings,这是必须的节点,下面每一个add子节点代表一个数据库。这个节点使用
System.Configuration.ConfigurationManager读取,因此不需要在configSections节点中额外配置。
名
称为dataConfiguration的section包括自定义的Database类(providerMappings节点)以及默认数据库配
置,DAAB
Configuration目录下的DatabaseSettings负责读取这个section的配置信息,DbProviderMapping类则负
责读取自定义的Database类配置信息。
上面配置示例3中的system.data节点是属于.Net框架配置内容,不需要DAAB自己处理,.Net框架将自动从应用程序配置文件和machine.config中获取。
Build Process
创建Database过程使用到了EnterpriseLibrary的Common库和ObjectBuilder组件,是DAAB中最复杂的部分。
首
先注意一下要创建的目标对象Database类,创建时都需要提供一个连接字符串和DbProviderFactory实例参数。SqlDatabase
需要System.Data.SqlClient.SqlClientFactory实例,OracleDatabase需要
System.Data.OracleClient.ClientFactory实例。
接下来,沿着创建处理可以找到驱动整个创建过程的关键点。
DAAB
中的DatabaseFactory类是给客户端使用的工厂类,可以指定名称创建Database对象,或者创建默认的。它的工作很简单,使用Ent
Lib
Common库中的ConfigurationSourceFactory创建一个默认的ConfigurationSource,通过这个
ConfigurationSource可以读取到DAAB的配置信息。然后用这个ConfigurationSource创建
DatabaseProviderFactory实例,用DatabaseProviderFactory来创建Database对象并返回。
DatabaseProviderFactory什么事情也没做,只是继承了Ent Lib Common的NameTypeFactoryBase。
NameTypeFactoryBase只是简单的调用Ent Lib Common的EnterpriseLibraryFactory。
驱动创建过程就是由EnterpriseLibraryFactory和ObjectBuilder一起完成的。
ObjectBuilder
采用Chain模式一步步创建目标对象,每一个步骤都是从ObjectBuilder中BuilderStrategy抽象类派生的一个具体创建策略类,
创建BuilderStrategy时提供的枚举指示ObjectBuilder在什么时候创建对象,例如PreCreation表示提前创建等。
EnterpriseLibraryFactory在静态构造方法中创建了4个BuilderStrategy,因此当它使用ObjectBuilder创建Database实例时就用下图所示的4个步骤进行:
1. 默认名称处理。
由ConfigurationNameMappingStrategy处理,主要是创建默认数据库时读取默认数据库名称;指定名称创建数据库时,它不做处理。
ConfigurationNameMappingStrategy
要求被创建对象的类上面有ConfigurationNameMapper这个attribute,指定一个用于获取默认实例名称的类型(必须实现
IConfigurationNameMapper接口)。DAAB中Database类使用
[ConfigurationNameMapper(typeof(DatabaseMapper))],指定为DatabaseMapper这个类,所
以ConfigurationNameMappingStrategy将创建DatabaseMapper,调用它的MapName来获取默认数据库名
称。
DatabaseMapper使用了DAAB的一个类DatabaseConfigurationView,包括后面要讲到的自定义工厂也会使用这个类。
DatabaseConfigurationView
为整个DAAB的配置提供一个全局视图,所以DAAB中需要获取配置的地方,都需要使用DatabaseConfigurationView完成,因此在
里面可以看到有获取DefaultName、ConnectionString、ProviderMapping等方法。在配置一节中有讲到
DatabaseSettings、DbProviderMapping等配置类,这些配置类只是对xml配置文件中相关节点的一种表示,可以把它们看作
是数据载体类;而DatabaseConfigurationView可以看作是一个服务类,它对所有配置信息进行封装,并提供一些转换服务,这在后面会
详细说明。
2. 单例模式处理。
由SingletonStrategy处理,这是ObjectBuilder采用一定机制,保证创建的目标对象为单例模式,DAAB不需要关心ObjectBuilder是怎样实现的。
3. 自定义工厂创建对象。
由ConfiguredObjectStrategy处理。DAAB创建各个Database实例时不是使用反射方式,而是通过自定义工厂类完成,ConfiguredObjectStrategy就是完成这个工作。
ConfiguredObjectStrategy
要求被创建对象的类上面有CustomFactory这个attribute,指定用哪个工厂类来创建目标对象。这个工厂类必须实现
ICustomFactory接口,ConfiguredObjectStrategy用反射创建工厂类,调用接口的CreateObject方法创建目
标对象。
DAAB中Database类使用[CustomFactory(typeof(DatabaseCustomFactory))],指
定为DatabaseCustomFactory这个类。但DAAB不能直接使用DatabaseCustomFactory来创建所有派生的
Database实例,否则不能扩展,因此DAAB加入了另外一个机制,即每个派生自Database的类通过attribute告诉
DatabaseCustomFactory使用哪个工厂完成创建操作,SqlDatabase通过
[DatabaseAssembler(typeof(SqlDatabaseAssembler))]指定为
SqlDatabaseAssembler,OracleDatabase指定为
OracleDatabaseAssembler,GenericDatabase指定为GenericDatabaseAssembler。这样
DatabaseCustomFactory在接口方法CreateObject中,只需要从派生自Database的具体类上获取attribute,
创建IDatabaseAssembler实例,并用它创建目标对象。
前面配置文件讲解中有提到,创建DAAB自带的
SqlDatabase、OracleDatabase时不需要使用providerMappings节点配置,只需指定providerName为
System.Data.SqlClient或者System.Data.OracleClient就可以。System.Data.SqlClient
和System.Data.OracleClient其实是machine.config
DbProviderFactories中使用的名称,DatabaseCustomFactory在通过
DatabaseConfigurationView获取ProviderMappings时,DatabaseConfigurationView先尝
试从配置文件中的providerMappings节点读取用户自定义的ProviderMapping;如果没有,则检查是否是
System.Data.SqlClient或者System.Data.OracleClient,如果是则使用内部创建的SqlDatabase或
OracleDatabase的设置;如果仍然不匹配,则使用GenericDatabase的设置。
4. 附加事件。
由
InstrumentationStrategy完成。DAAB支持附加事件,对Connection的Open和异常,以及Command、
DataAdapter的执行事件和异常进行监控,用于捕获异常进行日志记录,以及添加性能计数器用于调试分析目的。
InstrumentationStrategy为创建好的Database实例附加这些事件。
启用这个特性之后,需要对DAAB进行安装注册性能计数器等,这需要完全的系统权限,因为考虑到有些应用在部署时系统权限并不充分,所以DAAB默认是没有启用这个特性的,这里也不再深入。