ORM映射框架总结--数据库操作库(精修版)

1.       ORM数据库操作原理

前面已经介绍过了个人ORM映射框架中的三个核心库:

实体—数据库 映射特性关系:

http://www.cnblogs.com/qingyuan/archive/2010/04/02/1702998.html

 实体分析器:

http://www.cnblogs.com/qingyuan/archive/2010/04/05/1704546.html

Sql 语句生成组建:

http://www.cnblogs.com/qingyuan/archive/2010/04/16/1713658.html

 

至于这篇文章也就是这四个组件中的最后一个了------- 数据库操作库。目前.net 中对于数据库操作的模板或组件太多了,先不说微软的Linq to SQL,Linq to Entity, 还有开源组织的Hibernate 克隆版NHibernate,以及前些天突然冒出来的ALinq, 号称压过Linq,有时间再研究一下。其实这些看是强大的组件框架,在其外表下面都是如出一辙,他们都是在ADO.NET 中进行的封装。

不过在这个数据库操作中,都是ADO.NET进行数据库操作,但是对于对象的查询,都是泛化了的类型,也就是说我们不知道具体查询的是那张表,或者说是查询结果集是封装到那个对象,这就是本文要解决的问题。

  1. 2.       ADO.NET 介绍

感觉在这里进行ADO.NET的讲解似乎是多次一举,我相信ADO.NET是每个asp.net 程序员必会的基本技能。但是这里还是简单介绍其五个核心对象吧。

Connection: Connection 对象主要是开启程序和数据库之间的连结。没有利用连结对象将数据库打开,是无法从数据库中取得数据的。

Command:Command 对象主要可以用来对数据库发出一些指令,例如可以对数据库下达查询、新增、修改、删除数据等指令,以及呼叫存在数据库中的预存程序等。

DataAdapter: DataSetCommand 对象主要是在数据源以及DataSet 之间执行数据传输的工作,它可以透过Command 对象下达命令后,并将取得的数据放入DataSet 对象中。

DataSet:DataSet 这个对象可以视为一个暂存区(Cache),可以把从数据库中所查询到的数据保留起来,甚至可以将整个数据库显示出来。DataSet 的能力不只是可以储存多个Table 而已,还可以透过DataSetCommand 对象取得一些例如主键等的数据表结构,并可以记录数据表间的关联。DataSet 对象可以说是ADO.NET 中重量级的对象,这个对象架构在DataSetCommand 对象上,本身不具备和数据源沟通的能力。

DataReader: 当我们只需要循序的读取数据而不需要其它操作时,可以使用DataReader 对象。DataReader对象只是一次一笔向下循序的读取数据源中的数据,而且这些数据是只读的,并不允许作其它的操作。因为DataReader 在读取数据的时候限制了每次只读取一笔,而且只能只读,所以使用起来不但节省资源而且效率很好.

 

老生常谈的说了一遍ADO.NET 的五个核心对象,这里主要目的是为了更好的了解此篇文章。我想现在用Linq to SQL , Linq to Entity 的人太多了,或许都忘记了ADO.NET 的基本操作,其实我本人也是如此,一段时间不是用好多忘记了,这里再次复习一遍ADO.NET 能更好的理解此篇文章。

  1. 3.       数据库加载驱动操作接口

该数据库操作接口很简单,就是封装了一些数据库操作的常用接口和命令,并定义了一些数据库连接和关闭的方法,并控制了数据库事务提交和回滚的方法。不过在这里的方法实现中我并没有对事务进行处理,如果对本文有兴趣的人可以在后期的文章中关注,我会对此进行补充。

下面简单看看数据库加载驱动接口:

数据库加载驱动接口

 

 

 

这里数据库操作的常用对象都是采用的顶层的接口作为属性。至于为什么使用接口,它的好处已经说的太多了,次数也很多了,这里就不再多说。而public interface IDbProvider:IDisposable 集成这个接口,让程序自动管理对象,释放对象占用内存空间。

简单的看看SQL Server 数据库加载驱动操作类:

SQL Server 数据库加载驱动操作类

 

 

对于其他的数据库来说,使用操作的对象和命令都不同,他是它们都提供抽象出来了一个公共接口就是IDbProvider (数据提供加载驱动)。在接口中定义的每个get set 方法都进行了实现实现了一个属性的封装,并且提供了唯一对象的变量。(C#中属性的封装其实也就是一个get,set方法,所以在接口中能够这样定义方法)

 

  1. 4.       数据库的操作命令

ADO.NET 中查询数据库,返回的结果分为多种情况,而数据库的操作无非就四种情况增删改查.

而这四种情况又可以分为两大类:

(1). 数据库的修改操作: 数据的增加,修改,删除都对数据进行了操作,都有写的动作,对数据结构有了变动。

(2). 数据库的查询操作:数据库的查询,也就是读取数据,这个操作不会对数据的数据结构进行修改,因此这里可以单独分为一类。当然一类也就是比较复杂的一类了,查询的情况会很多不同。

 

下面是数据库操作的接口:

数据库操作的接口

 

 

 

a).查询返回受影响的行数

           查询返回受影响的行数,主要针对于数据库的增删改三个动作,在这三个动作中都是修改了数据库的结构。而SQL Server是关系型数据库,对于数据库的修改都是以数据行的形式来修改的。所以在对数据库进行结构修改时,返回的都是受影响的数据行数。

           SQL Server返回受影响行数方法实现

SQL Server返回受影响行数方法实现

 

 

从上面的源码可以看出,其实最终所有的方法都指向了一个方法,而这个方法有个特殊的地方就是bool isProcedure,用此参数可以判断这个操作的是SQL 语句还是存储过程。在这个方法中还使用到了一个特殊的参数provider.Command.Parameters,而Parameters集合是没有AddRange()方法的(而SqlParameters有这个方法),这个方法是使用的一个扩展方法,在.NET3.0 开始扩展方法为.NET 平台注入了新鲜的血液。让.NET 平台变的如此有魅力。

b).查询返回数据集的第一行第一列

           查询结果返回第一行第一列,我们用得最多的也就是聚合函数的使用了,聚合函数查询一般也就是查询返回一个结果。这里也就不再多讲解,看看方法实现的代码

查询结果返回第一行第一列

 

 

c).查询返回只读数据流

           查询只读数据流,返回的就是IDataReader,在ADO.NET 中查询返回只读数据流,是一种特别高效的读取数据的方式。作为.NET程序员我们一般都是使用的SqlDataReader 这个类,其实在程序架构的时候使用其父类接口IdataReader,程序更具灵活性,而这里的IdataReader 其实还有一个更重要的作用。那就是返回结果集合的时候,比如Ilist<T>。对于返回List<T> 结果在后面做介绍。

           返回IdataReader 源码

查询返回只读数据流

 

 

d).查询返回DataTable

           DataTable 相当于内存中小型数据库的表,查询的数据库结果将以表的结构保存在内存中,这个是ADO.NET中断开式数据库链接操作数据的一种有效方式。对于这种方式,查询数据还是比较方便的,但是对于数据库的增删改用DataTable 就比较麻烦了,可以查看ADO.NET数据库操作的具体内容,这里不做多的介绍。

           查询返回DataTable 结果集源码

查询返回DataTable

 

 

  1. 5.       IdataReader 转换集合

上面提到了,这个ORM映射框架可以查询返回Ilist<T>,程序开发过程中我们往往喜欢使用List 结合,特别是泛型集合。上面的数据库操作其中一种返回的是IdataReader于是我们就想使用IdataReader 转换为List集合,或者List<T>泛型集合。但是这里遇到了一个问题,如果转换为List<T> 泛型集合,我们如果给泛型类型赋值,在这个数据库的操作过程中是进行了高度的抽象的,我们是无法知道操作的具体类型,因此我们想到的是使用反射机制,在前面提到过的实体分析器,我们使用实体分析器分析了实体的详细信息,并缓存在内存中。因此我们有了这个信息就可以很好的和数据库对应起来。这个转换的核心工作就是反射赋值,下面看看实现方式:

IdataReader 转换为 T 泛型实体类:

IdataReader 转换为 T 泛型实体类

 

 

IdataReader 转换为Ilist<T> 泛型集合:

IdataReader 转换为Ilist 泛型集合

 

 

对于T 和 Ilist<T> 的转换其实是一样的过程。在这个转换的过程中有几个值得注意的地方。

    T entity = default(T);

default(T) 就好像我们实体对象赋初始值一样,在泛型中因为类型的不确定,所以语法规定了以这种方式赋初始值。

entity = EntityFactory.CreateInstance<T>(); 而这据代码是根据泛型类型创建了一个泛型实例,并在内存中分配空间。在这个里面定义了一个类,里面有很多方法,根据不同的情况,在动态创建对象,下面看看这个类的源码,感觉在对象抽象到一定程序的时候,这种动态创建对象的方式就非常有必要了。

动态创建对象的方式

 

 

封装集合其实就是根据实体分析缓存的信息,查找对应的数据流中的数据,动态的给对象赋值。在这个动态赋值的过程中有个地方,特别应该注意:

if (string.IsNullOrEmpty(reader[key].ToString()))

这句话就是讲读取的数据转换为字符串,在之前也提到过,.NET 中的数据类型和SQL Server 中的数据类型是不同的,如果reader[key] 的值为null,如果我上面的这句判断改为

If(reader[key]==null) 那么这里就会存在一个潜在的bug,上面已经说过了,数据库中的类型和.NET 中的类型是不一样的,需要使用DbNull 这个对象。更多类容不再多少,可以到网上查看更多的资料。

这个里面还有一个特殊的过程,那就是级联查询,当然这里只是做了级联查询父类的操作,至于级联查询子类集合的过程没有做处理,这个在后期的改版中做修改。

  1. 6.       下面举个小小的应用例子

没时间了,简单的介绍一下吧,不做详细的讲解了,后续在讲解。

在这个ORM框架中还定义了一个接口,源码如下:

数据库操作公共接口

 

 

上面的接口实现类:

数据库操作公共实体

 

 

自定义的数据库操作接口:

自定义操作数据库接口

 

 

 自定义数据库操作类:

自定义数据库操作类

 

 

这里的四段代码相信大家都能够明白其结构的定义,一般采用的方式每张表定义一个数据库操作接口和一个实现类,而这个操作接口是继承了公共的数据库操作接口,而且还是使用的泛型类型,使用泛型类型使得数据抽象话,而后面的操作过程刚好有与此泛化过程对应的处理过程,在此就保障了核心的一致,每个自定的数据库操作接口都会去继承公共的数据库操作类和实现自身的接口,因此每个自定的数据库操作类就具有了操作上面定义的操作的所有方法。在这儿结构中,感觉这个地方使用的非常秒,定义的公共接口中定义的操作方法几乎能后满足增删改查的要求,因此这样就实现了一个ORM操作数据库的完整功能,我们就可以不需要过多的去关心数据库到底是怎样去处理这些方式,我们的任何操作方式都是以对象的方式来操作,不用关系底层的细节实现。当然,任何一个ORM框架都不是能够替换所有的数据库操作,有些过于复杂的操作还是需要自定义sql语句来处理。我们只需在自定义的数据库操作接口中定义相应的方法即可,毕竟这个ORM映射框架也结合了自定义的处理过程,使得程序更加的灵活,可以从多角度去处理。看到这里,对于一个稍微大型的项目,相对于ADO.NET 操作是不是减少了很多代码。

 

这个东西毕竟是一个人写的,漏洞肯定也不少,自己也没有那么多的时间去测试,只能慢慢的等以后去修改,性能方面的提交,数据复杂度的处理等等问题,在后期的过程中会逐渐来解决这些问题,并与大家分享。如果大家有任何疑问可以留言,或者好的建议可以随时联系我。在此谢谢

 

 (注: ORM涉及内容比较多,后续期待,有兴趣的可以与本人探讨) 

posted @ 2021-10-21 18:12  dreamw  阅读(114)  评论(0编辑  收藏  举报