剖析 .Net 下的数据访问层技术
zhangxuefeng(原作) CSDN
自从 .NET 真正走入开发人员那天起,“效率”两个字就一直成为众多程序员津津乐道的话题。无论是从开发模式( Cross Language )、系统框架( .NET Framework ),还是各种使用方便的工具( VS.NET ),无一不体现出了它的胜人一筹。
同时,在另一方面, .NET 是否可以真正胜任企业级应用( Enterprise Application )开发的重任,却依然争论不断,褒贬不一。
通常来说,对于一个企业级应用,需要考虑的方面很多,如安全、性能、伸缩性、易用性等。在本文中,作者更愿意与大家一起探讨 .NET 下数据访问层的相关技术,这可能是在多层架构( n-Tier Architecture )诞生之日起就受到广泛关注的敏感话题,而对于大部分开发人员来说,这也可能是项目中最让人沮丧的部分,甚或引起争议最多的部分。
在以下论述中,为统一起见,作者暂时将数据访问层简称为 DAL ( Data Access Layer )。
l 分析问题
简单统计分析后,就不难发现, DAL 之所以让人畏惧,并非出于技术本身的问题,甚至恰恰相反,很多开发人员认为这是最没有技术含量的部分之一(就作者经历的大小项目来看,该层所占的开发时间一般较短,也是很多开发人员不愿意承担的“苦差”),只是架构需要或者某些思想作怪(如:为 DAL 而 DAL )才加入了这所谓的第四层 (传统三层架构并没有提出 DAL 思想)。
DAL 的提出,确实对传统的架构模式提出了巨大挑战,加入的目的肯定也是希望借其进一步提高生产效率,在这种模式下,理想情况是:大部分开发人员从此摆脱 DBA 之苦,甚或彻底断绝与数据库的直接关系, SQL 之痛将离我们而去,整个 OO 世界从此清静。
不过,理想归理想,能否成为现实则需通过项目检验。
接下来,作者试图分析比较流行且较有代表性的几种解决方案,看看能否从中得出一些有价值的结论,并为我们今后在设计与实现 DAL 时提供一些借鉴。
u ADO.NET
首先,提到 .NET 下的 DAL ,立刻映入眼帘的就是 ADO.NET 。
没错,几乎所有的 DAL 解决方案 (请允许作者使用 Solution 而非
Framework )都必须从它发展而来,没得选择,这也是具有 .NET 特色的
实现方式(相比较 J2EE )。
排除商业因素及 CLR 本身的需要, ADO.NET 真正带给我们的东西
不多 , 值得一提的也就 DataSet (就作者经历的项目来说 , 使用更多的是
DataTable 和 DataView )。从微软早期的内存数据库( Memory Database )
鲜有人问津到今天的 DataSet 大行其道,这其中的曲折实非片言只语所
能道尽,总之,有一点可以肯定,正是有了 DataSet 这种选择, .NET 下
的 DAL 才能象今天这般百花齐放,大家的思路才能更趋开阔。
Duwamish
这方面有很多好的 Sample ,最经典的莫过于微软大力推荐的企业级开发套餐: Duwamish 。
对于希望学习 .NET 下 DAL 设计的朋友,这是一个不错的起点,这
方面的完整剖析,大家可以参考 “ CSDN 开发高手, 2003.11 ” ,本文不
再赘述。
作者自己参与的一个项目中就使用了 Duwamish 方案,当时限于工
期,感觉这是一个很好的参考,没做深入分析就开始设计了。现在回想
起来,发现还是有很多不足之处。
举个简单的例子, Duwamish 方案中并没有考虑 Cache Management ,
而这对于企业级应用来说,某些时候就是一个不得不考虑的问题;另一
方面,虽然 Duwamish 中告别了 SQL 语句(全部采用存储过程实现),
但数据库痕迹依旧十分明显,比如:某些字段名称的定义,关联表名称
的定义等等。
还有一个十分头疼的问题是在开发过程中体现出来的。一开始,那
些比较简单的数据表还比较容易实现,到了一些包含相互关系的数据表
时,我们的 DAL 工程师就感到了压力,到后来,几乎又做了一遍 DBA
在数据库建模时早已做过的工作,只不过,这次将数据库脚本换作了
C# 实现(或者说:将数据库结构换成了表面上 具有 OO 特色 的 DataSet )
而已。
可能, Duwamish 的实现比较经典,但在实际应用中,有时并不意
味着 Best Practice 。就拿我们的项目来说,虽然成功交付,但无论从模
型复用角度,还是开发效率来说,都不能算很成功。套用一句流行语:
其实我们可以做得更好!
PetShop
ADO.NET 上另一个值得参考的 DAL 实现就是鼎鼎大名的 PetShop 。
当然了,与 Duwamish 相似,名气大未必真的实用。 PetShop 虽然
弥补了 Duwamish 在某些方面的不足,例如:通过 Factory 支持多种数
据库存储,引入了 Cache 机制,提供了更为便利的 SQL Helper ,但也同
时带来了另一些问题。其中,最麻烦的就是 SQL 语句的引入,而且还
是针对不同数据库存储的不同 SQL 语句(主要是 SQL Server 与 Oracle
的参数表示方式不同)。
另一方面, PetShop 虽然没有使用 DataSet 而代之以更为简洁的普通
实体对象( Model ),但它还是将 DataReader 的结果转换到了包含实体对
象的列表集合中供离线使用,从这个意义上说,可谓换汤不换药 。甚至,
在某些场合,例如:需要进行数据过滤,或者在主从数据间导航,反而
更为不便(此时,简单的 Collection 或者 List 是无法满足需求的, DBA
与 DAL 开发人员只能再提供其它的方法来达到目的)。
从上述两个例子中,我们可以看出,即使在微软的开发团队中,也
没有能够在 DAL 这个问题上达成一致。这方面的更详细信息,有兴趣
的朋友可以参考如下文章:
http://www.microsoft.com/china/community/Column/67.mspx 。
实战
上面剖析的两个解决方案,让我们看到了它们各自的优势与不足,而企业级应用的复杂环境也不太可能要求一个放之四海而皆准的框架就能解决所有难题,因此,只能根据具体情况具体分析。
作者曾经参与一个( .NET )大型外包项目的开发工作,有幸一睹其 DAL 的设计思想,深感震撼,在此与各位朋友一起共同探讨。
以 SQL Server 所带 Northwind 数据库为例,如下就是一段基于该 DAL 的调用代码(作者做了一些名称上的调整):
// 根据 EmployeeID 返回其 Title
boEmp = new EmployeeDAL();
boEmp.Keys[“Emp_ID”] = 1; // 注意:实际字段名为: EmployeeID
boEmp.Select();
string strTitle = boEmp[“Emp_Title”]; // 注意:实际字段名为: Title
……
// 根据 City 返回所有符合条件的 Employee
boEmp = new EmployeeDAL();
boEmp.Keys[“Emp_City”] = “Seattle”;
boEmp.Select(); // 注意:该方法与上面的调用完全相同
DataTable dtEmp = boEmp.Table;
如果不考虑对象创建(可以采用 Object Pooling 或者 Cached Object )以及调用后的处理,实际的代码只有两行!
更让人吃惊的是,上述 EmployeeDAL 类没有任何真正意义上的实现代码,仅仅是声明了类名,然后从一个通用基类继承而已!!
最优雅的地方还不在于此,实际上,就算在那个基类中,也根本看不到 SqlConnection 或者 OracleAdapter 之类的帮派之争。
相信大家也猜出来了,没错,它是借鉴了 PetShop 的实现,采用了 Factory 模式来保证 DAL 可以适用于不同的数据库存储。不过,这种实现与 PetShop 还是有很大的区别:至少,它没有产生不同的 SQL 语句,更没有出现不同的参数调用方式( SQL Server 中一般使用“ @ ”符号, Oracle 中一般使用“:”符号),所有帮派一视同仁!
这其中,当然得益于 Factory 的实现技巧,但更重要的因素还在于设计方式的精妙。其实,在 .NET Framework 中,已经提供了这种设计方式的基石,说白了,就是 System.Data 中的那些 Interface (如: IDBConnection , IdataAdapter 等)。
在这样的设计基础上,我们针对每一个 DAL 类,就不再需要为不同的数据库存储提供不同的数据存取实现了。例如:在 PetShop 中,针对订单数据需要实现 Order 类,很自然的,系统为 SQL Server 与 Oracle 分别实现了 Order 类并使用不同 Provider ( SqlClient , OracleClient )提供的方法进行操作。而在实际调用时, PetShop 通过 Factory 模式动态创建真正的 Order 类并激活相应的方法,一个面向不同数据库存储的方案就跃然纸上。
其实, PetShop 这种方案已经比较灵活了,如果更能省去“撰写不同 Order 类”之苦,那就真的送佛送到天了 J 。而所有这些功能,在作者所参与的这个项目中,已经完全搞定了!
至于上面的“ EmployeeDAL (当然,包括其它所有 DAL 类)没有任何真正实现代码”,只不过玩了一个小小的配置技巧而已:将不同的 DAL 类与相关的 Stored Procedure (请注意:不是 Table 或 View )按照 Namespace 分别存储到 XML 文件中。
可能大家已经看出来了,理论上,甚至只需要一个 DAL 类就可以完成上述所有的工作!但在实际操作中,不同的 DAL 类可能还是有一些数据处理上的细微差别(比如:数据校验,格式转换等)。
总的来说,在这样一个大项目中,不可能要求所有开发人员(除了 DBA , DAL Framework Developer )都去了解 ADO.NET 的方方面面,虽然作者对此颇有研究,但在这个项目中,却从头至尾只用到了两个类 : DataTable , DataView (甚至连 Transaction 都无需了解)!
其它
结束 ADO.NET 剖析前,不得不提提 DataReader 与 DataSet 间的兄弟
之争。
就作者所看过的资料,几乎所有的都建议实际情况具体分析,剩下
很少很少的则全凭个人习惯决定。
在学习 ADO.NET 时,作者也是抱着这样的想法,并反复牢记资料
上总结的那些条款(就像当年学习 GOF 23 条时那样,几乎可以倒背如
流了 J ),想到终有一日也可在 ADO.NET 下大展神威了。
可惜现实不随人愿,连续做了几个项目,无论规模大小,竟然全部
采用了 DataSet 解决方案!
此时,再回头看看学习 ADO.NET 时打开最为频繁的 PetShop 项目,
两相一比较,这才看出些许端倪。
简单的说, PetShop 采用了如下这种“曲线救国 ”的方式来实现数据
交换:
DataReader 获取 数据 => 创建 数据实体类 => 根据字段类型填充 数
据实体类 => 将数据实体添加 到列表类中(仅针对返回超过一条数据的
场合)
(补充:采用数据实体类或者集合类可以比较方便的实现 Cache Manament ,
而普通的 DataReader 由于其数据读取方式限制,无法满足这种需求)
这个过程与 DataAdapter.Fill() 所所产生的效果大同小异 ,只不过,
在 Fill() 中 DataAdpater 自动创建 DataReader 去获取数据,之后创建
DataTable (相当于数据实体类),并根据字段类型填充 DataTable ,当然
,如果可能返回多条记录, DataTable 完全可以处理,就没必要去实现列
表操作了。
可能读者马上产生了疑问:既然如此, PetShop 中为何还需要数据实
体类呢?
这其中还是有一些差别的。
首先,数据实体类是轻量级的 structure ,一般仅包含数据字段,没有
什么操作方法,这比 DataTable 或者 DataRow 还是有一些性能上的优势
(在数据量不大时可以忽略不计);另一方面,数据实体类的操作相对
简单,不需要开发人员具备任何 ADO.NET 知识(其实就 DataTable 来
说,这也不算什么问题),点点属性就可以了。
不过,根据作者的实践来看,这两方面似乎还不足以使人转而使用
DataReader 方案,理由列举如下:
(1) 对于数据量较大的场合,可以采用分批读取的方式,这有点类似 DataGrid 的数据分页效果;
(2) 对于简单的数据,实体类还能应付,一旦涉及关联数据,就只能另外撰写方法了。而所有这些,在 DataSet 中是非常容易处理的(对于企业级应用,大部分情况都需要处理比较复杂的数据);
(3) DataTable “天生”就支持数据集合操作,这样的特性比“集合 + 实体 ”的混合模式( PetShop )更容易控制,也更自然;
(4) 实体类在声明时需要确定所有数据类型,当进行数据填充时,就需要 DataReader 再次关注实体所对应的数据类型,不能有丝毫差错!在这方面, DataTable 就显得非常方便,操作时只需要一次类型关注 即可;
(5) DataSet 解决方案可以非常方便的支持序列化操作(如: Remoting , WebServices ),同时,与 XML 的关系更是亲密无比,这对于和其它系统的交互来说也是至关重要的。
分析过一些技术和方案,相信读者朋友已有一些体会。值此收官之际,如果非要在这里提供一个“综上所述”,那作者的建议就非常明确:
在企业级应用开发中,尽可能 的采用 DataSet ( DataTable / DataView ) + Cache Management 解决方案!
其它开发中,只在如下 4 种情况 才考虑使用 DataReader (就作者经验来说,大部分使用 DataReader 都属第 2 种情况):
(1) 对资源要求比较苛刻的场合,这里的资源主要指内存和数据库连接;
(2) 希望在读取数据库返回结果集时作自定义处理,例如:在读取一条记录后立刻终止处理,或者在读取时作计算操作。
(提示:这种情况类似于 XML 中的 SAX ( Simple API for XML )技术,无需一次性读入所有 XML 数据即可进行操作;相反的, DOM ( Document Object Model )则要求必须装载所有 XML 数据后才能开始操作( MSXML4.0 已开始允许只读取 XML 文档部分数据即可开始操作,这是后话) ! )
(3) 只希望得到返回记录数或者返回记录的部分字段,如:
string GetNameByID(int nID) // 根据员工 ID 返回员工姓名,这里只需要
// 读取姓名字段;
(提示:这种情况一般可以通过执行特定的查询或存储过程直接解决)
(4) 出于某些方面的考虑(例如: n-Tier 系统中严格区分各 Layer 间的职责),无法(或者禁止)通过数据库本身进行查询过滤,这时就只有使用 DataReader 在读取时进行过滤操作!
(提示:虽然 DataView 也能达到这种目的,但它的过滤前提是必须读取到所有返回数据,所以性能上不如 DataReader !)
u O/R Mapping
O/R Mapping 的全称是: Object Relational Mapping ,主要目的是在传统 RDBMS 与 OO Language 之间建映射关系,从而使开发人员彻底脱离数据持久 这片剪不断理还乱的苦海。
关于 O/R Mapping 或者近来比较热门的 O/X Mapping (大家可以参考 “程序员, 2004.01 , P86 ”) ,可能需要专门的文章进行详细论述,本文的目的主要是对现有方案的优缺点进行简单剖析以及提供一些实践中的参考信息。
相比较 J2EE 平台, .NET 下的 O/R Mapping 可谓没什么历史,至今还尚未有经过考验的成熟的可用方案。但是,随着各大厂商的重视以及开源项目的如火如荼, .NET O/R Mapping 的步伐也开始慢慢跟上,使这块本属于 J2EE 的领地加入了新的竞争对手(会不会使更多的开发人员投入 .NET 阵营? J ),也让众多疲于在 SQL Clause 或 ADO.NET 中来回奔命的 DAL 开发人员看到了“光明之路”。
接下来,就让我们一起看看在这片比 ADO.NET 更广阔的土地上有些什么值得探讨的 Solution 。
Ø Open Source
开源方面一直与 .NET 保持一定距离, O/R Mapping 更是寥寥无几,但就作者的下载试用和源码分析来看,个人以为如下的两个解决方案还是有一定参考价值的: OPF , OJB 。
有关这两个开源项目的简介,大家可以参考 “程序员, 2004.01 , P13 ” 。
OPF 的全称是: O bject P ersistent F ramework 。
OJB 的全称是: O bJ ect Relational B ridge 。
在实现手法上,这两个方案的思路完全不同,具有各自的代表性。
OPF 走的路线有点类似于 Typed DataSet 或 Borland ECO (请参考下面的介绍),实现比较简单,提供更多的源码级控制 ;而 OJB 的实现则类似于 Microsoft ObjectSpaces (请参考下面的介绍),采用了配置文件 的方式,相对就比较复杂了。
这两个方案的基本框架如下所示:
OPF :
从图中不难看出:
(1) Persistent 类扮演了 DataSet 的角色,除了常规的对象数据操作外,还可以设定不同对象间的关系(如主从关系,集合关系等,这一点在 Borland ECO 所生成的代码中也可略见一二),这也是上文所说“提供更多源码级控制 ”的原因所在;
(2) PersistentSqlDataManager 则扮演了 DataAdapter 的角色,通过预先设置的 Commands 来执行真正的数据库操作;在实际撰写的 employee data manager 中,开发人员确实需要提供基本的 SQL 语句,就像在 SqlCommond 中设置的那样( Borland ECO 则更进一步,以 OCL 代替了 SQL );
(3) ObjectBroker 的作用非常重要,它是对象与数据间的桥梁, RegisterPersistent 方法建立了这种虚拟 ( Object )与现实 ( RDBMS )间的关系 ;
(4) 在 employee business object 的声明中,对象属性与数据库字段的对应关系是通过 .NET Attribute 机制体现的,所以修改起来还是比较方便的,虽然相比配置文件的方式显得不够灵活(请参考 OJB 的介绍) , 比如:需要重新编译,开发人员不得不关注数据库字段等。
OJB :
从图中不难看出:
(1) 该方案的实现比较复杂,但用户需要实际撰写的代码变少了(只需要编写 employee business object ),这其中的关键就在于引入了配置文件 ;同时,由于配置文件的引入,我们在 hello world application 中也不需要调用类似 OPF 解决方案(请参考上文的 OPF 类图)中的 RegisterObject 方法,所有这一切(甚至包括数据库连接信息),系统都已了如指掌!
(2) 该方案中, SQL 命令通过 Criteria 类被彻底替代,而 QueryFacade 则充当了 Adapter 的功能,通过 PersistenceBroker 这一真正的 Command 与数据库进行通信;
(3) 无论是 repository.xml 配置文件,还是 Criteria 、 QueryFacade 类,我们都可以在 ObjectSpaces (请参考下面的介绍)中找到类似的实现(难道是巧合?),同时,作者个人以为,这种方式也更符合 O/R Mapping 的精神,减轻了开发人员的负担!
(4) OJB 还有一个非常 cool 的工具“ repositorygen.exe ”,可以用来生成 repository.xml 配置文件(同样的,源码无偿奉上 J ),这一点,甚至连 ObjectSpaces 都没能做到(想想那么多字段、属性、关联、映射,简直可以让人发疯 J )!
Ø Microsoft ObjectSpaces
这是一个在几年前就让众多 .NET guy 伸长脖子激动不已的技术。就作者来说,那个时候,只要一提起这个话题,一般都是在 J2EE guy 的嘲笑声中悻悻而归,恨不能自己也搞个 ENB (相对 EJB )或者 NCMP (相对 CMP )什么的。
终于,我们可以在 .NET Framework 1.2 (可在 VS.NET 2004Whidbey 或 Yukon 中找到,目前都是 Beta 版本)中一睹其“芳容”了 J 。
首先,让我们看看用 ObjectSpaces 写出的代码是什么样子(依然使用上面的 employee 例子):
// 初始化 ObjectSpace
SqlConnection conn = new SqlConnection("Data Source=localhost;
Integrated Security=SSPI; Database=Northwind");
ObjectSpace os = new ObjectSpace("map.xml", conn);
// 根据 EmployeeID 返回其 Title
Employee oEmp = (Employee)os.GetObject (
new ObjectQuery (typeof(Employee), "ID = 1"));
// 注意:实际字段名为: EmployeeID
string strTitle = oEmp.Title;
……
// 根据 City 返回所有符合条件的 Employee
ObjectSet oSet = os.GetObjectSet (
new ObjectQuery (typeof(Employee), "City = '”Seattle'"));
// 注意:返回的不是 DataTable ,而是对象集合
foreach (Employee oEmp in oSet)
{
…… // 注意:在这里可以对 oEmp 做任何操作
}
针对上面第二段代码,还有一种解决方案,就是以 ObjectReader 替代 ObjectSet ,这其中所包含的差异,类似于 ADO.NET 1.0 (包含 ObjectSpacesd 的 ADO.NET 又称为 ADO.NET 2.0 )中的 DataSet / DataTable 与 DataReader 间的不同(不得不佩服 Microsoft 在前后一致性上表现出的老谋深算 J )。
仔细分析上面的代码,就可以发现它和前面讨论的 OJB 有惊人的相似点( OJB 中作者只画了基本类图,但足可看出这种思想上的接近)!
例如: ObjectSpace 类基本上提供了 OJB 中的 QueryFacade 功能; ObjectQuery 类基本上提供了 OJB 中的 Criteria 功能;同时,两种解决方案又不约而同的使用了配置文件来存储 O/R Mapping 信息;而应用程序一般也就通过这 2 个类进行数据操作,非常方便。稍微有些区别的可能是在数据返回格式上(这一点, ObjectSpaces 考虑得更细致,可以参考上面的代码),但这已经对实际的代码实现影响不大了。
如果将 ObjectSpaces 下的调用代码与前面给出的那段在 ADO.NET 下撰写的代码作个比较,不难看出, ObjectSpaces 给出的代码更易阅读和理解,就算不熟悉 ADO.NET 整体架构的开发人员,也可轻松上手(唯一涉及 RDBMS 的代码只有建立数据库连接时需要)。对于已经熟悉 ADO.NET 或曾接触过 O/R Mapping (如: J2EE 下的 Hibernate )的朋友来说,真可谓小菜一碟!
从 .NET Framework 1.2 文档中可以知道, ObjectSpaces 总共提供了 3 个命名空间,整体结构非常清晰:
System.Data.ObjectSpaces
System.Data.ObjectSpaces.Query
System.Data.ObjectSpaces.Schema
ObjectSpaces 、 Query 已在上面的代码中见识过,从名字中可以猜出,它们主要负责向外提供基本访问接口(如查询、增 / 删 / 改等)和解析各种查询条件(如对象过滤等), Schema 命名空间则主要用来操作 O/R Mapping 配置文件,并为其它两个命名空间中的类提供服务。
在 ObjectSpaces 中, O/R Mapping 配置文件主要指 map.xml ,这个文件的名字是可以随意更换的,比较类似 OJB 中的 repository.xml 。另外两个分别描述数据库结构和对象结构的配置文件也非常重要: RSD.xml ( Relational Schema Definition ), OSD.xml ( Object Schema Definition )。可以将它们理解为 Typed DataSet 中的 XSD 文件,没有它们,所有的数据 / 对象 Mapping 和 Validation 都将是“非法”的 J !
本文中,作者不准备对 ObjectSpaces 来个深度探索,也不会提供什么 Sample 说明其优越性,这方面, .NET Framework SDK 早已为大家提供了丰富套餐。
作者只是希望,如果从 DAL 的角度来分析, ObjectSpaces 技术能为我们带来什么,是否意味着从此告别 DataReader / DataSet ,抑或为开发人员带来了新的烦恼?
好处不多说,仅举数例即可明了:
(1) ObjectSpaces 全部采用对象方式访问数据,大大缓解了很多开发人员的 SQL (或者说 RDBMS )恐惧症;
(2) 对于比较简单的数据库结构变化,只需要修改配置文件即可,无需重新编译代码(较之 OPF 中将映射关系以 .NET Attribute 方式封装于代码中,显得更加灵活、方便);
(3) 对于比较复杂的数据库结构变化,由于只涉及对象操作,所以修改的工作也要比以前简单许多;
(4) 采用了 O/R Mapping 配置文件后,数据库设计与 DAL 开发可以分别进行,相互的影响也降到了最低点;
不足则是我们更须关注的话题:
(1) 目前版本不支持中文 (永远的话题 J )查询,不爽!
(2) 当前版本仅支持 SQL Server 2000 以上 版本的数据库系统,弱(这是个很耐人寻味的限制,有兴趣的读者不妨想想到底是什么原因)!
( 1 、 2 引自 .NET Framework SDK Document ,就这两点已排除了很多跃跃欲试的朋友。而作者参与的 .NET 项目虽不受 1 影响,但由于经常使用 Oracle ,就不得不暂时忍痛割爱了 J )
(3) 性能问题 。虽然 ObjectSpaces 也提供了类似 DataReader 的功能( ObjectReader ),但毕竟需要进行一次数据强类型填充,无论如何会有损失,如果返回数据量变大,将是一个不得不考虑的问题;
(4) 还是性能问题 。 map.xml 是个好东东,但如何优化对它的访问以及进行正确的 Validation (基于 RSD.xml 、 OSD.xml )毕竟需要时间,甚至在某些时候(数据库结构比较复杂),这会造成比第 3 点更为严重的后果;
说了些不足,其实也无须过于担心,毕竟,没有十全十美的解决方案,怎么取舍就看你自己的决定了。
本章最后,作者给出了一个自己的总结,可供您参考一二。在所有的分析完毕之后,作者也试图结合自己的实践提供“我的方案(撰写中)”,希望能给各位读者带来帮助。
作者简介:
“ 本文作者张雪峰 是 毕博全球开发中心 的高级开发工程师。他目前在中国上海 毕博全球开发中心 Core/EAI 部门工作,从事 .NET 技术的研究以及相关项目的开发。可以通过 xuefeng.zhang@bearingpoint.com 与他联系。 ”
查看更多关于剖析 .Net 下的数据访问层技术的详细内容...