收藏本站 收藏本站
积木网首页 - 软件测试 - 常用手册 - 站长工具 - 技术社区
积木学院 > 程序开发 > 系统架构 > 正文

系统架构师谈企业应用架构之数据访问层

来源:互联网 日期:2014-10-07 12:00

一、上章回顾

上篇我们简单讲述了服务层架构模式中的几种,并且讲解了服务层的作用及相关的设计规范,其实我们应该知道,在业务逻辑层中使用领域模型中使用服务层才

能发挥出最大的优势,如果说我们在业务逻辑层还是使用非领域模型的模式话,服务层的作用仅体现在解耦作用。其实在业务逻辑层采用领域模型时,我们前面说的持

久化透明的技术,其实我们可以通过服务层来做,我们在服务层中处理领域对象信息的持久化操作。当然本篇可能不会深入讨论持久化透明的具体实现,后面会单独开

篇来讲述,我们先来回顾下上篇讲解的内容:

上图大概描述了上篇我们讲解的内容,如果您想要详细的了解服务

层的相关内容,请参考:系统架构师-基础到企业应用架构-服务层,后续我们将会对一些前端的服务层还会进行扩展的讲解,请大家提出报告意见和建议。

二、摘要

本篇将主要以系统中与数据库存储介质进行交互的数据访问层进行详细的介绍讲解,我想这块也是大家比较熟悉也是经常在项目中一定会用到的部分,我们知道

数据访问层通常我们都把这块的内容提升出来,写成通用的类库,在我们前面讲解的分层架构的系统中,基本上可以说业务对象中的数据都要通过数据访问层将业

务数据持久化到存储介质中。其实目前有很多的好的ORM框架已经很好的实现了数据访问层,而且得到了很广泛的应用,当然我们本篇也会以这些通用的框架为例举

例说明数据访问层中的一些设计模式。本章将会以下列几个典型关注点展开去讲:

1、数据访问层的职责及与其他组件的交互。

2、如何设计自己的数据访问层。

3、实现数据访问层必须满足的4个基本要求,持久化CRUD、查询服务、事务服务、实现并发等。

4、结合目前流行的几类框架分析框架提供的数据访问层功能的优劣。

下面我们将针对上面的几个关注点依次展开去说,希望能通过本文的讲解,让您对数据库访问层有个更深刻的认识和了解。

三、本章大纲

1、上章回顾。

2、摘要。

3、本章大纲。

4、数据访问层介绍。

5、如何设计数据访问层。

6、实现数据访问的四项原则。

7、本章总结。

8、系列进度。

9、下篇预告。

四、数据库访问层简介

本节将会主要针对数据访问层的功能及职责进行讲解,分析之前在业务逻辑层中的四种模式与数据访问层之间的关系。我们闲来看看数据访问层与业务逻辑层中的

四种模式之间的关系。

我们在本节中的讲解主要是以领域模型为例进行分析讲解,因为只有领域模型模式,我们才能将数据访问层抽离出来,分成单独的层,这样能够做到领域对象持久

化透明。接下来我们来看看数据访问层都需要提供什么要的功能及数据访问层本身的职责是什么。

数据库访问层是唯一知道如何操作存储介质的入口,可以这么来说,基于数据访问层之上,我们调用数据库访问层提供的方法,我们就能完成数据的存储与读取,

所以我们可以知道,数据访问层应该是与数据库直接是独立的。还有就是我们的数据访问层如何能实现不同类型的数据库的动态的切换,而我们不需要修改任何的程序

功能等,可能我们在开发的过程中都会遇到这样的问题。所以我们希望可以对数据访问层完成动态的配置,通过不同的配置项完成对象数据库访问的切换,这里我想大

家都是比较熟悉的,通过XML配置文件来完成数据库的切换,前面我们说了我们的需求,是必须实现无缝的数据库的切换,那么我们如何实现呢,这里我们可以通过定

义一个数据库访问接口,然后通过实现不同的数据库的细节,来实现这样的切换。目前很多流行的框架都是采用这样的方式来实现数据库的动态切换。当然有时候我们

的项目中肯能不让我们使用开源的通用框架,这时候我们可能就需要自己去实现这些数据访问层的具体细节。

当然数据访问层都必须能够将应用程序中的数据持久化到存储介质中,通常我们使用的数据都是关系型的数据库,但是我们知道我们在程序的开发中,通常采用的

模型都是对象模型,那么如何实现对象模型与关系模型直接的互相的转换就显得非常的重要。当然这是数据访问层的重要功能。通常来说,业务逻辑层及服务层不了解

数据库访问的具体细节,他们都需要通过数据访问层来实现数据的交互。一般来说在领域模型中,数据访问通常都是在服务层中进行调用的,而业务逻辑层并不关注数

据持久化,所以我们前面说的持久化透明的方式也是由此方式来实现。

上面我们啰嗦了一大堆,基本上说了数据访问层的基本需求功能:

下面我们来看看数据访问层的几个基本职责

首先、数据访问层应该提供基本的持久化操作CRUD的操作,我们知道数据访问层是唯一能操作数据库的一层,因为我们在设计时需要注意,系统的其他层不能

包含操作数据库的相关功能

这里我们通过类图可以看到,通过提供与类对应的数据库操作类来提供

相应的持久化操作方法,当然这不是唯一方式,可行的方式有很多。我们后面会详细讨论。

其次、应该提供能够满足类中信息的读取操作,一般情况下来说我们经常使用的是,根据主键查询某个对象的信息,或者是查询所有的记录,或者是根据条件返

回满足的集合。

当然我们这里定义的查询类可能是通用的形式,通过泛型的形式来做。但是我们知道

领域模型中肯定会有引用对象的情况,那么对于这样的情况我们如何来做呢?我们一般是通过延迟加载的形式来处理这样的要求,我们后面会依次讲解。我们来看看上

面的通用形式的简单代码格式:

  1. public class QueryHelper  
  2. {  
  3. /// <summary>  
  4. /// 根据主键返回对象  
  5. /// </summary>  
  6. /// <typeparam name="T"></typeparam>  
  7. /// <param name="key"></param>  
  8. /// <returns></returns>  
  9. public T GetObjectByKey<T>(object key)  
  10. {  
  11. return default(T);  
  12. }  
  13. /// <summary>  
  14. /// 获取指定类型对象的总记录数  
  15. /// </summary>  
  16. /// <typeparam name="T"></typeparam>  
  17. /// <param name="key"></param>  
  18. /// <returns></returns>  
  19. public int GetCount<T>(object key)  
  20. {  
  21. return 0;  
  22. }  
  23. /// <summary>  
  24. /// 返回指定类型的所有对象集合  
  25. /// </summary>  
  26. /// <typeparam name="T"></typeparam>  
  27. /// <returns></returns>  
  28. public List<T> GetAll<T>()  
  29. {  
  30. return new List<T>();  
  31. }  

再次、数据库访问必须提供事务的管理,可以说不提供事务操作的数据访问层就没有办法使用,因为这样的数据访问层构建的系统是不安全的。特别是批量持久

化的过程中,事务不但能够减少与数据库操作的次数,而且根据事务的四个特性可以提供更好的安全性。我们来回顾下事务的四个特性:

我想我这里就不用一一解释了,大家都明白的。我们在数据访问层的设计中是通过引入

“工作单元”来实现事务管理的,工作单元后面会讲述到工作单元内提供的方法及事务性。

最后、数据访问层必须提供处理并发的功能,我们在系统访问的人较多的情况时肯定会出现并发的情况,数据访问层如何处理这样的情况就显得极其重要了,在

一个多用户并发的系统中,通过前面提到的事务来处理,这时候可能就会出现数据库完整性的问题,例如这样的情况,一个用户现在在编辑自己的个人信息,例如将生

日修改为1985年3月20日,这个用户对应的ID是298,这时候他只是修改了,但是还没有提交,此时管理员也修改了,比如说修改了ID为298的这个用户信息的地址或

者其他信息,并且提交,此时,用户将自己编辑的生日提交了,那么数据库中对应的ID为298的数据信息就会是最新修改的数据信息,那么之前管理员修改的数据信息

就会发生丢失,虽然是修改的可能字段不是同一个字段,这就和我们底层实现的数据访问层有关,当然如果说我们在数据访问层实现了,只更新修改过的数据列的值的

话,那么可能不会存在这样的情况,当然这就和我们底层实现的数据访问层的机制有关。

下面我们通过图形的方式来说明,更容易理解:

我们来看看可能的几种方案,可以对这样的并发做出相应的处理?

当然这里只是提供了几个简单可行的办法,当然如果大家还

有更好的办法,可以告诉我,不胜感激。

当然上面的四个基本职责是我们在数据访问层必须提供的,还应该提供缓存机制,延迟加载等等包括一些性能方面的优化的设计等,这些都在后面讲解吧。

下面我们来看看数据访问层与其他层直接的关系与交互,我们前面说过,在领域模型下,业务逻辑层中的数据的持久化都是通过服务层来完成的,下面我们来看

看各层之间的关系。我们先来看看服务层与数据访问层之间的关系。

服务层与数据访问层之间进行交互,服务层通过DTO与UI层进行交互,服务层通过组织业务逻辑层中的对象来实

现业务流,然后通过调用数据访问层将业务流中的相应数据进行持久化,通过数据访问层来初始化领域模型。置于直接在表现层中使用数据访问层的功能,我们通常是

不推荐这样做的,一般我们不会这么做的,我这里就不详细的阐述。

五、如何设计数据访问层

本节将详细的讲述如何设计出自己的数据访问层,满足上述的几个基本要求,那么可以说就算完成了基本的数据访问层的功能,其实如果我们从头开始开发一个

这样的数据访问层将是非常大的工作量,目前流行的很多的ORM框架已经提供了丰富的数据访问层的功能,能够非常好的满足上述的几项职责。当然本节还是会结合代

码来说说数据访问层的具体实现。

我们前面讲述了数据访问的3个基本的功能需求,数据库独立性,可配置性及持久化对象模式(对象模型与关系模型的转换),我们这里先看如何实现数据库的独立

性,我们提出满足数据库的无缝迁移,通过XML配置文件,配置不同的数据库链接来实现这样的功能,那么首先我们需要定义针对不同数据库具体实现,才能完成后续

的操作,既然我们这里要降低耦合,那么根据我们前面的面向对象的设计原则与规范知道,我们推荐使用面向接口的编程的方式,来尽量的降低耦合性。我们来看看具

体的代码。首先我们定义一个通用的数据访问层的接口。

  1. /// <summary>  
  2. /// 数据访问层统一接口  
  3. /// </summary>  
  4. public interface IDALInterface  
  5. {  
  6. //CUD 持久化操作  
  7. /// <summary>  
  8. /// 创建新的对象  
  9. /// </summary>  
  10. /// <param name="model"></param>  
  11. /// <returns></returns>  
  12. int Create(object model);  
  13. /// <summary>  
  14. /// 更新对象  
  15. /// </summary>  
  16. /// <param name="model"></param>  
  17. /// <returns></returns>  
  18. int Update(object model);  
  19. /// <summary>  
  20. /// 删除对象  
  21. /// </summary>  
  22. /// <param name="model"></param>  
  23.  /// <returns></returns>  
  24. int Delete(object model);  
  25. //R 查询服务  
  26. /// <summary>  
  27. /// 查询所有记录  
  28. /// </summary>  
  29. /// <typeparam name="T">泛型模型</typeparam>  
  30. /// <returns></returns>  
  31. IList<T> GetAll<T>() where T : class,new();  
  32. /// <summary>  
  33. /// 查询满足条件的集合  
  34. /// </summary>  
  35. /// <typeparam name="T"></typeparam>  
  36. /// <param name="whereCondition"></param>  
  37. /// <returns></returns>  
  38. IList<T> GetListByQuery<T>(WhereCondition whereCondition) where T : class,new();  
  39. /// <summary>  
  40. /// 返回总行数  
  41. /// </summary>  
  42. /// <typeparam name="T"></typeparam>  
  43. /// <returns></returns>  
  44. int GetCount<T>();  
  45. /// <summary>  
  46. /// 返回满足条件的总行数  
  47. /// </summary>  
  48.  /// <typeparam name="T"></typeparam>  
  49. /// <param name="whereCondition"></param>  
  50. /// <returns></returns>  
  51. int GetCount<T>(WhereCondition whereCondition);  
  52. /// <summary>  
  53. /// 根据主键返回对象模型  
  54. /// </summary>  
  55. /// <typeparam name="T"></typeparam>  
  56. /// <param name="key"></param>  
  57. /// <returns></returns>  
  58. T GetModelByKey<T>(object key) where T : class,new();  
  59. //事务  
  60. /// <summary>  
  61. /// 是否事务执行  
  62. /// </summary>  
  63. bool IsTransaction  
  64. {  
  65. get;  
  66. }  
  67. /// <summary>  
  68. /// 开始事务  
  69. /// </summary>  
  70. void BeginTransaction();  
  71. /// <summary>  
  72. /// 提交事务  
  73. /// </summary>  
  74. void Commit();  
  75. /// <summary>  
  76. /// 回滚事务  
  77. /// </summary>  
  78. void Rollback();  

这里定义了基本的几个简单方法,当然其中并没有包括并发的处理,后面会讲到这块的处理方案的实现, 前面介绍了几种可行的实现方式。接口定义好了之后,

数据层的具体代码我这里就不一一的定义贴出来了,因为每种不同的数据库类型就要分别实现,我们这里讲解2中不同类型的实现思路吧,

我们这里讲解2中实现动态创建具体数据访问组件的方式,我们先来讲讲插件模式。

插件模式

插件模式:插件模式就是通过外部配置文件中读取要创建的组件的类型信息,然后调用组件服务,插件模式的关键点就是服务不会与具体实现联系起来,在我们的

分层结构中的解释就是,服务层中调用数据访问层中的组件服务,服务层不关系具体的调用方式,服务层只关心服务。而具体的数据访问组件是通过配置文件来动态的

创建,当然这就需要使用.NET中的反射的功能。我们来看个图形画的描述:

反射工厂通过读取配置文件中具体的数据配置项及数据访问的具

体服务组件类型,通过反射工厂来动态的创建,好了我们来看看实例代码及配置文件。

  1. <?xml version="1.0" encoding="utf-8" ?> 
  2. <Connection> 
  3. <ConnectionItem key="connectionString" 
  4. value="Data Source=.SQLEXPRESS;Initial Catalog=EasyStore;User ID=sa;Password=123456" />
  5. value="Data Source=.SQLEXPRESS;Initial Catalog=EasyStore;User ID=sa;Password=123456" /> 
  6. <DALType key="DALType" 
  7. value="DAL.SQLServer" />value="DAL.SQLServer" /> 
  8. <Assembly key="Assembly " 
  9. value="DAL.SQLServer" />value="DAL.SQLServer" /> 
  10.  </Connection> 

上面的配置文件中的ConnectionItem 节点中配置了数据库访问的链接字符串,DALType 定义了数据访问层组件的类型。我们下来看看反射工厂的示例代码实

现。

  1. public class DALHelper  
  2. {  
  3. private static IDALInterface instance;  
  4. public static IDALInterface GetDAL()  
  5. {  
  6. string assambly = XmlHelper.getVlaue("Assembly");//这里应该是自定义的读取XML节点的方式  
  7. string type = XmlHelper.getVlaue("DALType");  
  8. Assembly asm = Assembly.Load(assambly);  
  9. instance = (IDALInterface)asm.CreateInstance(type);  
  10. return instance;  
  11. }  
  12. }  

我们接下来看看如何使用这个数据访问层去实现相应的持久化操作:

  1. public class TestService  
  2. {  
  3. private IDALInterface DAL;  
  4. public TestService()  
  5. {  
  6. DAL = DALHelper.GetDAL();  
  7. }  
  8. public void Create(Test test)  
  9. {  
  10. //相应的判定操作  
  11. //创建对象  
  12.  DAL.Create(test);  
  13. }  

这样就实现了在服务层对数据访问层的调用操作,这里是通过接口调用的方式来实现。我们再来看看控制反转的实现方式吧。

控制反转

控制反转我们在设计规范与原则中有过讲解,控制反正通过动态的将组件注入到引用该组件的对象中的形式,然后让引用该组件的对象使用组件的服务,DI依赖注

入可以看作是控制反转的一个应用实例,我们可以把控制反转看作是一个原则。

下面我们来看看我们如何通过控制反正的方式来实现数据访问层的平滑迁移。当然我们知道,肯定是通过动态注入的方式来实现,当然目前主流的也有很多的IOC动

态注入框架,下面我们将会借助一些框架来说明如何实现这样的功能。

本文将以Enterprise Library5.0为例进行讲解动态注入的形式。我们先来看看配置文件的设置

我们来看看通过一个中间类去实现相应的注册代码:

  1. /// <summary>  
  2. /// 用于动态完成代码注入的公共类  
  3.  /// </summary>  
  4. public class IOCContainer  
  5. {  
  6. private static IUnityContainer container;  
  7. private UnityConfigurationSection section;  
  8. public void InitIOC()  
  9. {  
  10. container = new UnityContainer();  
  11. section = (UnityConfigurationSection)System.Configuration.ConfigurationManager.GetSection("unity");  
  12. section.Configure(container);  
  13. }  
  14. public static IDALInterface GetDAL()  
  15. {  
  16. return container.Resolve<IDALInterface>();  
  17. }  

通过上述代码我们实现了,动态的创建数据访问层组件实例,下面我们来看看依赖注入的方式去完成相应的持久化的功能。我们来看看服务层的代码

  1. /// <summary> 
  2. /// 测试服务层  
  3. /// </summary> 
  4. public class TestService  
  5. {  
  6. private IDALInterface DAL;  
  7. public TestService(IDALInterface dal)  
  8. {  
  9. DAL = dal;  
  10. }  
  11. public void Save(Test test)  
  12. {  
  13. DAL.Create(test);  
  14. }  

这里我们是采用构造函数注入的方式来实现数据访问层的动态注入的,当然除了构造函数注入,还有其他的方式,我们这里只是举例说明,一般来说依赖注入有

如下的几种形式

具体的我这里就不举例说明了,大家可以网上查查有很多的例

子,我们平时也常用这些方式。

六、实现数据访问的四项原则

在第四节中我们讲解了数据访问层的四个原则,那么我们在自己的数据访问层中如何实现这几个原则呢,我想针对第一个原则持久化的原则,我们前面只是简单

的讲解如何实现数据库的独立性,下面我们先来看看持久化的操作,也就是我们说的CUD的操作,并不包括具体的查询服务,查询服务也是我们数据访问层必须提供四

个原则之一,我们后面都会讲解,我们先来看看CUD的实现。我们在做持久化服务的时候,一般情况下,我们会定义一个统一的数据访问层接口,然后提供持久化服

务,事务等等,通常有一些数据访问层共性的部分,我们都通过一个抽象类来实现,抽象类将实现接口中的部分功能,然后通过定义一些抽象成员函数,让具体的数据

访问层去实现相应的功能。我们这里以上节我们定义的IDALInterface为例讲解基类的简单实现。

我们将原来的接口层进行相关的优化操作将CUD操作单独抽取出来,通过ICUDMapper接口来定义

  1. /// <summary> 
  2. /// 数据库持久化访问器  
  3. /// </summary> 
  4. public interface ICUDMapper  
  5. {  
  6. //CUD 持久化操作  
  7. /// <summary> 
  8. /// 创建新的对象  
  9.  /// </summary> 
  10. /// <param name="model"></param> 
  11. /// <returns></returns> 
  12. int Create(object model);  
  13. /// <summary> 
  14. /// 更新对象  
  15. /// </summary> 
  16. /// <param name="model"></param> 
  17. /// <returns></returns> 
  18. int Update(object model);  
  19. /// <summary> 
  20. /// 删除对象  
  21. /// </summary> 
  22. /// <param name="model"></param> 
  23. /// <returns></returns> 
  24. int Delete(object model);  
  25.  } 

然后我们来看看基类接口层的简单实现,作为所有数据访问层的父类

  1. public abstract class BaseDAL : IDALInterface,IDisposable  
  2. {  
  3. protected abstract ICUDMapper GetMapper();  
  4. //CUD 持久化操作  
  5. /// <summary>  
  6. /// 创建新的对象  
  7. /// </summary>  
  8. /// <param name="model"></param>  
  9. /// <returns></returns>  
  10.  public int Create(object model)  
  11. {  
  12. return GetMapper().Create(model);  
  13. }  
  14. /// <summary>  
  15. /// 更新对象  
  16. /// </summary>  
  17. /// <param name="model"></param>  
  18. /// <returns></returns>  
  19. public int Update(object model)  
  20. {  
  21. return GetMapper().Update(model);  
  22. }  
  23. /// <summary>  
  24. /// 删除对象  
  25. /// </summary>  
  26.  /// <param name="model"></param>  
  27. /// <returns></returns>  
  28. public int Delete(object model)  
  29. {  
  30. return GetMapper().Delete(model);  
  31. }  
  32. #region IDisposable 成员  
  33. /// <summary>  
  34. /// 是否数据库访问组件资源  
  35.  /// </summary>  
  36. public void Dispose()  
  37. {  
  38. }  
  39. #endregion  

当然这里只贴出实例代码。当然我采用这样的方式,就是利用我们之前的一篇:Step by Step-构建自己的ORM系列-开篇这篇中的反射的思想,大家可以看看

特性+反射的思路,我这里的数据持久化访问器也是类似的操作,可能底层的实现就是这样的方式。

具体的数据持久化访问器如何动态的生成SQL语句,缓存优化等各方面的内容,我们本篇可能不会深入的讲解,我还是想将这块放在ORM系类篇深入讲解。

当然我们其实可能极端的做饭就是为每个领域模型中的对象建立一个数据持久化映射器,完成映射,我这里则是通过创建数据库的统一模式,在具体的映射器中,

通过反射取得数据对象的映射信息。我们来看看实现的思路吧,具体代码我就不贴了

大体的流程就是上面说的了,细节肯定还有很多要注意的地方。

下面我们来看看查询服务的实现:

我想一般的系统80%的时间数据库执行的操作是查询,而20%的时间在完成写入和修改的操作,当然我这里不是绝对的说法。我们希望有一个工具帮我们自动完

成基本的查询服务,而不是我们手动的去书写,因为我们发现对大部分的数据集合而言,有一些共性的操作,例如获取某个主键值的对象的信息,或者是获取数据库表

中的总行数,或者是返回数据库表的所有记录,并且如何将关系数据库中的关系模型转换为对象模型,这都是查询服务中应该提供的基本功能。下面我们来看看简单实

现吧。

我想我们还是参考前面的方式,我们将IDALInterface层中的查询服务进行抽象分离,将查询服务单独提出来放在接口IQuery中。代码如下:

  1. public interface IQuery  
  2. {  
  3. /// <summary>  
  4. /// 查询所有记录  
  5. /// </summary>  
  6. /// <typeparam name="T">泛型模型</typeparam>  
  7. /// <returns></returns>  
  8.  IList<T> GetAll<T>() where T : class,new();  
  9. /// <summary>  
  10. /// 查询满足条件的集合  
  11. /// </summary>  
  12. /// <typeparam name="T"></typeparam>  
  13. /// <param name="whereCondition"></param>  
  14. /// <returns></returns>  
  15. IList<T> GetListByQuery<T>(WhereCondition whereCondition) where T : class,new();  
  16. /// <summary>  
  17. /// 返回总行数  
  18. /// </summary>  
  19. /// <typeparam name="T"></typeparam>  
  20. /// <returns></returns>  
  21.  int GetCount<T>();  
  22. /// <summary>  
  23. /// 返回满足条件的总行数  
  24. /// </summary>  
  25. /// <typeparam name="T"></typeparam>  
  26. /// <param name="whereCondition"></param>  
  27. /// <returns></returns>  
  28. int GetCount<T>(WhereCondition whereCondition);  
  29. /// <summary>  
  30. /// 根据主键返回对象模型  
  31. /// </summary>  
  32. /// <typeparam name="T"></typeparam>  
  33. /// <param name="key"></param>  
  34. /// <returns></returns>  
  35. T GetModelByKey<T>(object key) where T : class,new();  
  36.  } 

我们来看看在基类中的实现。查询服务的相关实现

  1. /// <summary>  
  2. /// 查询服务组件  
  3. /// </summary>  
  4. /// <returns></returns>  
  5. protected abstract IQuery GetQuery();  
  6. #region IQuery 成员  
  7. /// <summary>  
  8. /// 查询所有记录  
  9. /// </summary>  
  10. /// <typeparam name="T">泛型模型</typeparam>  
  11. /// <returns></returns>  
  12. public IList<T> GetAll<T>() where T : class,new()  
  13. {  
  14. return GetQuery().GetAll<T>();  
  15. }  
  16. /// <summary>  
  17.  /// 查询满足条件的集合  
  18. /// </summary>  
  19. /// <typeparam name="T"></typeparam>  
  20. /// <param name="whereCondition"></param>  
  21. /// <returns></returns>  
  22. public IList<T> GetListByQuery<T>(WhereCondition whereCondition) where T : class,new()  
  23. {  
  24. return GetQuery().GetAll<T>();  
  25. }  
  26. /// <summary>  
  27. /// 返回总行数  
  28. /// </summary>  
  29. /// <typeparam name="T"></typeparam>  
  30. /// <returns></returns>  
  31. public int GetCount<T>()  
  32.  {  
  33. return GetQuery().GetCount<T>();  
  34. }  
  35. /// <summary>  
  36. /// 返回满足条件的总行数  
  37. /// </summary>  
  38. /// <typeparam name="T"></typeparam>  
  39. /// <param name="whereCondition"></param>  
  40. /// <returns></returns>  
  41. public int GetCount<T>(WhereCondition whereCondition)  
  42. {  
  43.  return GetQuery().GetCount<T>(whereCondition);  
  44. }  
  45. /// <summary>  
  46. /// 根据主键返回对象模型  
  47. /// </summary>  
  48. /// <typeparam name="T"></typeparam>  
  49. /// <param name="key"></param>  
  50. /// <returns></returns>  
  51. public T GetModelByKey<T>(object key) where T : class,new()  
  52. {  
  53. return GetQuery().GetModelByKey<T>(key);  
  54. }  
  55. #endregion 

当然根据不同的数据库可能定义的查询语句的格式不同,但是返回的结果的形式却可以定义成通用的形式。这样我们就可以实现比较通用的查询服务,也有很好

的通用型和扩展性。当然我们这里还可以添加分页的支持等,只是添加的条件有限制,实现方式还是相同。

下面我们来看看数据访问层功能必须职责之事务性,我们都知道事务性的几大特性,通过事务性来提供数据的安全性。我们这里给出一种思路去实现这样的事务

性,我们在数据访问层中定义一组事务单元,通过一个列表维护这些事务单元,当执行提交时,我们将这个事务范围内的所有事务单元进行提交,否则不进行真正的提

交操作。我们来看看吧,我们在之前的IDALInterface中已经定义了事务相关的几个方法,我们这里同样抽出来,进行分解,抽出来一个单独的接口ITransation,具体

代码如下:

  1. public interface ITransaction  
  2. {  
  3. /// <summary>  
  4. /// 是否事务执行  
  5. /// </summary>  
  6. bool IsTransaction  
  7. {  
  8. get;  
  9.  }  
  10. /// <summary>  
  11. /// 开始事务  
  12. /// </summary>  
  13. void BeginTransaction();  
  14. /// <summary>  
  15. /// 提交事务  
  16. /// </summary>  
  17. void Commit();  
  18. /// <summary>  
  19. /// 回滚事务  
  20. /// </summary>  
  21.  void Rollback();  

基类中的代码如下:

  1. #region ITransaction  
  2. /// <summary>  
  3. /// 是否事务执行  
  4. /// </summary>  
  5. public bool IsTransaction  
  6. {  
  7. get 
  8. {  
  9. return GetTransaction().IsTransaction;  
  10. }  
  11. }  
  12. /// <summary>  
  13. /// 开始事务  
  14. /// </summary>  
  15. public void BeginTransaction()  
  16. {  
  17. GetTransaction().BeginTransaction();  
  18. }  
  19. /// <summary>  
  20. /// 提交事务  
  21. /// </summary>  
  22.  public void Commit()  
  23. {  
  24. GetTransaction().Commit();  
  25. }  
  26. /// <summary>  
  27. /// 回滚事务  
  28. /// </summary>  
  29. public void Rollback()  
  30. {  
  31. GetTransaction().Rollback();  
  32.  }  
  33. /// <summary>  
  34. /// 返回事务单元列表  
  35. /// </summary>  
  36. List<TransationUnit> list  
  37. {  
  38. get;  
  39. }  
  40. /// <summary>  
  41. /// 执行事务单元的操作,执行数据操作并提交  
  42. /// </summary>  
  43. /// <param name="unit"></param>  
  44. void Excute(TransationUnit unit);  
  45. #endregion  
  46. /// <summary>  
  47. /// 事务组件服务  
  48. /// </summary>  
  49. /// <returns></returns>  
  50.  protected abstract ITransaction GetTransaction(); 

事务组件中添加了特殊的事务单元,用来存储事务执行的操作CUD,还有就是要事务执行的数据对象,当然事务对象中的CRD操作就是使用前面讲解的CRD操作

的方式,我们来看看吧,我们来看看事务单元的形式。

  1. /// <summary>  
  2. /// 事务单元  
  3. /// </summary>  
  4. public class TransationUnit  
  5. {  
  6.  /// <summary>  
  7. /// CUD枚举  
  8. /// </summary>  
  9. public enum CUDEnum  
  10. {  
  11. Create,  
  12. Update,  
  13. Delete  
  14. }  
  15. private CUDEnum _cudType;  
  16. private object _model;  
  17. public TransationUnit(object model, CUDEnum cudType)  
  18. {  
  19. _model = model;  
  20. _cudType = cudType;  
  21. }  

我们在事务处理中,我们只执行事务列表中的操作,置于非事务列表中的单元我们将不做任何处理,所以我们只要是事务执行的事务单元,我们必须将指定操作类

型,当然我们还可以更灵活,我们通过在事务单元中设置属性判定是否在事务中,如果不在事务中,我们执行数据持久化操作,如果在事务中,我们则添加到事务列表

中,因为我们在提交时总会循环执行事务列表中的事务单元。当然我这里只是一个简单的思路,抛砖引玉,希望大家有更好的想法,可以跟我交流,或者给我提出建

议,那样我就感激不尽了。下面我们来看看数据访问层中的并发的问题,我们如何去应对,有没有什么完美的方案呢?

前面我们举例子说过,有3中方式,我们这里举例说明:

通过字典存储值发生变化的列,完成更新变化列的操作。这样至少可以避免2人同时更新,造成最近更新的内容覆盖先前更新的内容,虽然可能更新的是不同的数

据列,我们这里只提供实例代码:

另外2种实现方式也差不多,一种是在数据库表中添加一列版本号,然后在数据更新时必须加上之前取出来的对象的版本号,然后一并同主键作为条件,完成更

新,这样至少可以保证数据的完整性。另外一直就是通过对象代理,我们将要更新的对象的字段的旧值,放在代理中,然后在生成更新语句时,我们将对象代理中的旧

值也添加到where查询条件中,这样我们通过限制条件的方式,来维护数据的完整性。我们来看看简单代码:

  1. public class EntityProxy  
  2. {  
  3. public string Name  
  4. {  
  5. get;  
  6. set;  
  7. }  
  8. public object Tag  
  9. {  
  10. get;  
  11. set;  
  12. }  
  13. public int ID  
  14. {  
  15. get;  
  16. set;  
  17. }  
  18. public string Unit  
  19. {  
  20. get;  
  21. set;  
  22.  }  

假设上面的几个数据列是我们要更新的数据库列,那么我们只需要在UPdate语句中,附加这四个数据库列字段条件就好了,这样如果发现更新结果返回的是0,那

么代表更新失败。通过上面的几种形式,我们来看,如果允许的话,我们还是推荐在数据库表中添加一个版本号的形式来处理更新,这是最高效的形式,可以通过时间

戳的形式来生成版本号。

七、本章总结

本章主要简单阐述了,数据访问层的基本功能,必要的四个职责,及数据库访问层的简单的设计思路与实现思路,当然我这里没有提供完整的实现,具体的实

现,我想我会在ORM系列中提供完整的代码,当然目前可能必须讲完架构后,回头我会拾起来那部分进行详细的讲解,好了我想本文的内容都比较浅显易懂,大家都能

够迅速的掌握,如果您有更好的思路或者设计方案,那么很希望您能提出来交流,这将是我莫大的荣幸。 

作者:CallHot-何戈洲

出处:http://www.cnblogs.com/hegezhou_hot/

关于作者:专注于微软平台项目架构、管理和企业解决方案。熟悉设计模式、极限编程、架构设计、敏捷开发和项目管理。现主要从事WinForm、ASP.NET、等方面的项目开发、架构、管理工作。如有问题或建议,请多多赐教!

推荐阅读

 

热点信息

 
强悍的草根IT技术社区,这里应该有您想要的! 友情链接:b2b电子商务
Copyright © 2010 Gimoo.Net. All Rights Rreserved  京ICP备05050695号