DAAB工作原理、代码结构上的一些注解(转)

 

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

//create a default database instance 
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

Database db = DatabaseFactory.CreateDatabase(); 
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

Database db = DatabaseFactory.CreateDatabase(); 
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的配置文件如下:

<configSections>
  
<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,如下配置:

<configSections>
  
<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):

<configSections>
  
<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(非常简单),从另外的文件中读取配置信息:

public static Database CreateDatabase()
{
    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默认是没有启用这个特性的,这里也不再深入。

 

摘自:http://www.cnblogs.com/wenanry/archive/2009/03/27/1423257.html

posted @ 2011-08-30 18:05  sunnyboy  阅读(476)  评论(0编辑  收藏  举报