第13讲:深入ADO.NET开发—高级数据访问技术
2005.4.8 付仲恺
基础预习
熟悉Microsoft ADO.NET
了解Web Services基础开发
了解.NET文件集操作
议题
并发问题
连接池
事务
可抽取的数据访问层
并发性问题
在可断开连接架构中必须要面对并发问题
当两个(或多个)用户获取并修改相同的记录,然后试图同时维持各自的修改时,将会发生冲突:脏读u,不可重复读,虚幻读
当提交多个更新的时候,会造成部分更新无法正确完成
悲观与乐观(ADO.NET构建)并发锁
悲观锁:无论读写都加锁
乐观锁:只有写加锁,读不加锁(ADO.NET)
ADO.NET中对并发性问题的处理
与DataAdapter对象相连接的DataSet对象使用乐观锁来处理记录内容冲突
DataAdapter配置向导能够修改Update和Delete语句以检测是否发生了并发问题
当ContinueUpdateOnError为false(缺省值)时,在发生第一个冲突的时候会抛出DBConcurrencyException异常
无论是true还是false,对于有并发性问题的数据行都会取消更新操作,它们之间的不同是为true时还会更新其它行,为false时会马上抛出异常。
捕获该异常并且通知用户,或者处理该异常或提示用户下一步需要做出哪些选择
演示一
处理并发性问题
先同时打开两个窗体,都Load出一样的数据,然后修改左边的1,2行数据保存后,修改右边的1,2行数据,这时就会抛出并发性错误的异常,并且显示0行被改变,也就是没有完成更新。
它只对于第一个并发的问题弹出对话框,而对于第二个并发问题并没有提示错误,因为它一旦抛出异常,就不会对后面的数据进行操作了。
另一种演示是把ContinueUpdateOnError的属性设置为True,然后进行更新。GetErrors()方法能获取出产生冲突的数据行。
左边更新了3行数据,右边更新了3行数据,其中有两行数据有并发冲突。
结果并没有抛出异常的对话框,并且没有发生并发性冲突的更新会更新成功。
连接池
连接池可以极大地提升性能和可扩展性
避免由创建连接所带来的大量消耗
通过在连接字符串中添加以下属性来调节连接池
Pooling=true;Max Pool Size=5;Min Pool Size=3;
上面的字符串将打开连接池,并且确保在连接池中至少存在着3个连接,并且最多为5个连接
当达到最大连接时,打开新连接的请求将排队一段可配置的时间
默认等待时间15秒,如果超过15秒还未获得连接,就会抛出超时异常。
当使用OleDbConnection时,如果要关闭连接池在连接字符串中添加"OLE DB Services=-4"
注意
连接是通过对连接字符串精确匹配的法则被池化的。池化机制对名称-值对间的空格敏感
例如,下面的两个连接字符串将生成单独的池,因为第二个字符串包含了一个额外的空字符。
"Integrated Security=SSPI;Database=Northwind"
"Integrated Security=SSPI ;Database=Northwind"
Close方法将连接放回连接池
Dispose方法则直接销毁连接,而不放回到连接池中
演示二
连接池
简单连接池,最后没有Close是为了演示查看连接池中的连接。打开SQL Server管理器,能看见连接池中的三个等待激活的链接。
高级连接池,创建多线程调用Go方法,启动线程并让线程等待。
排队超过15秒,抛出超时异常
我们可以通过修改连接字符串调大超时时间或者调大最大连接数
50个线程不停地获取SQL版本号,其中每个线程执行100次,所有线程都执行完之后,共会获取5000次SQL的版本号信息。
使用连接时我们不到万不得已不要打开连接,打开连接后也应该及时关闭它。
事务
ACID原则:原子性,一致性,隔离性,持久性
事务行为要么完成所有动作,要么不做任何动作
同时提交或者回滚所有的修改
不是所有的SQL语句都允许出现在事务中
例如创建数据库的操作、修改表的操作,是不允许出现在事务中的
ADO.NET事务只能应用于单一连接中
SqlTransaction对象从SqlConnection.BeginTransaction()方法中返回
SqlTransaction类
SqlTransaction trans=conn.BeginTransaction();
实例化事务对象
隔离级别枚举(IsolationLevel):Chaos,ReadCommitted,ReadUncommitted,RepeatableRead,Serializable,Unspecified
Serializable是最高级别的隔离
隔离级别越高,越能更好处理并发性问题。ADO.NET默认使用ReadCommitted级别的隔离。
处理工作:Insert,Update,Delete
要么提交所有的修改:trans.Commit();
要么如果发生错误,撤回所有的修改:trans.RollBack();
隔离级别
Serializable提供了最高的隔离级别,但却只有最低的执行效率
Serializable是序列化的方式,也就是以串行的处理方式一个挨一个进行访问,所以效率最低。
演示三
本地事务操作
高级事务
为了执行跨数据库事务,需要通过System.EnterpriseServices向分布式事务协调器(DTC)注册链接
分布式事务也能进行本地化操作,但是它会相对有较大的开销,因为本地事务处理不需要与分布式协调器进行交互。
在中间层Web服务上添加:System.EnterpriseServices.Transaction属性
TransactionOption枚举:Disable,NotSupported,Required,RequiresNew,Supported
TransactionIsolationLevel枚举:Chaos,ReadCommitted,ReadUnCommitted,RepeatableRead,Serializable
Serializable是最高级别的隔离
连接字符串中的Enlist项表明连接是否参与到事务操作中
为true表示参与事务操作
当连接字符串中Enlist=false时,可以调用SqlConnection.EnlistDistributedTransaction方法以执行事务操作
对分布式事务的控制方法
使用AutoComplete属性对方法进行注释。如果方法正常执行完毕,则自动提交事务,而如果发生异常则自动执行回滚操作
调用ContextUtil类的静态方法SetComplete或SetAbort来实现事务提交和回滚操作
演示四
跨数据库事务
要使用COM+事务需要添加EnterpriseServices引用。
方法上加了标签,表示需要执行事务的操作。方法的最后会抛出一个异常,让事务自动回滚。只有中间的Enlist为false且没有调用EnlistDistributedTransaction方法的操作不会回滚,其它都会随事务回滚。
使用事务的注意事项
在一组操作中,只有在需要保证ACID特性时才使用事务
降低事务的粒度,以最小化维持数据库锁的时间
不要为单个SQL语句使用事务处理。SQL Server自动把每个语句作为单个事务处理执行
层次化应用程序架构
多层应用程序将代码分为不同的逻辑代码层
非常普遍的设计方案是3层架构设计:表示层,业务逻辑层和数据访问层
合理的层次化设计带来了很多益处:代码复用,可扩展性,封装性,松散的耦合性,高聚合性
我们能够设计一个可抽取的数据访问层来完全抽象数据存储
层次化应用程序架构——概念
3层是在逻辑上划分的,在物理上不一定要按这种严格的划分。
数据访问层——概念
位于应用程序的业务逻辑与数据存储之间的代码层
包含了应用程序所有的数据访问代码;提供CRUD操作函数
业务逻辑层只与数据访问层有松散的耦合关系
可抽取数据访问层
可抽取数据访问层为应用程序提供良好的可扩展性
设计可抽取数据访问层可以很容易地修改,更新,数据存储而不需要重新编译其它层
通过接口实现松耦合度
通过基于接口的工厂模式以实现可抽取组件
将数据访问层从业务逻辑层中完全抽象出来
基于接口的编程——概念
接口类似于合同
基于接口的编程消除了从实际代码中实现的方法的耦合性
提供了多态机制
实现“可抽取”组件
可抽取组件——概念
.NET允许通过Assembly.Load()以及Assembly.CreateInstance()来动态加载文件集和类
通过配置指向具体的数据访问层类
“新的”或者“改进的”(不同)组件可以“插入”到现存的应用程序中
不需要从新编译那些依赖接口的代码
非常低的耦合意味着极大的可扩展性
演示五
可抽取数据访问层
GetDaL是数据访问层,它根据传入参数,动态加载文件集创建对应实例
这三个接口的实现其实都是组件。我们把三种不同颜色访问的方法抽象成了IDaL的接口,业务逻辑层并不需要关心是访问哪一个对象或者组件,它只需调用GetDaL即可获取对应颜色。
总结
并发问题是在可断开链接数据模型中必须要处理的问题
连接池能够改进性能和扩展性
事务确保数据的更新的原子性,持续性,隔离性和持久性
可抽取数据访问层是通过松耦合实现可扩展性的强有力的方法
2010.10.22