问题提出
数据访问层(DAL)的目标创建一些以便业务层来调用的类和方法。我们之前总是用GridView来绑定DataSet和DataReader,但是在稍微大点的项目开发中DAL不能直接和用户
界面打交道。
一般来说,DAL是用来和数据库和BLL打交道的,也就是处理BLL和数据库的中间。数据以什么形式在DAL和BLL之前传递有很多的争论。不同的人有不同的意见,数据传递的形式有:DataSet,强类型的DataSet,DataReader,自定义实体。在介绍Ling to Sql之后,大家心里会有清晰的答案。在以前的开发中,我们一般是采用ADO.NET来和数据库打交道,那么就需要我们的开发人员对ADO.NET有一定的比较深入的了解,但是当我们用Linq to Sql之后,我们可以很方便的使用DataContext来与数据库拉打交道,而不需要我们懂得很多的ADO.NET的知识,但是在Linq to Sql的背后还是在采用ADO.NET来和数据库交互的。
还有就是事务处理的问题。关于事务的概念,相信大家都清楚,我也不赘述了。事务处理在什么地方实现有如下意见:在存储过程中直接用SQL语句来写;在DAL层处理,
在BLL层处理。当然,每一种的选择都有各自的理由和利弊。还有一点要注意的是:不要把事务处理的代码到处写,如在DAL层中写一点,在BLL中写一点。
设计方案
在设计方案中实际上就是提供几个选择来解决之前我们提出的问题。以下就是两个选择:
1. DAL只要是执行CRUD操作,CRUD是就是:Create,Read,Update,Delete.在.NET Framwework中提供了很多和数据库打交道的ADO.NET类和方法,如
SqlConnection,SqlCommand,SqlCommand.ExecuteNonQuery()等,用过ADO.NET的朋友应该清楚这些常用的类,我这里也不罗嗦。
2. SqlHelper
用过ADO.NET的朋友应该知道,在我们开发过程中,很多时候写ADO.NET代码的时候,代码结构和功能都是大同小异的,所以基于此,微软就开发了Microsoft Data Access Application Block,只要我们调用其中的一些方法,传入一些参数就行了,不需要我们再去写那些繁琐的ADO.NET代码,因为这个数据访问块都已经封装好了。其中一个最重要的类就是SqlHelper.这个类是个静态类,提供了很多的方法,如下:
ExecuteNonQuery
ExecuteDataset
ExecuteReader
ExecuteScalar
FillDataset
上面 方法的设计包含了很多OO的思想。我们来看看ExecuteNonQuery方法 ,其他的方法的设计思想和方式一样的:
上面前3个方法都是采用了一个连接字符串的参数,而接下来的3个方法是采用了一个连接对象为参数,最后的3个方法采用了一个事务对象为参数。这些方法在调用存储过程时提供了很大的灵活性。 例如:
调用代码如下:
还有一个问题要注意就是更新时的同步问题。例如,用户从数据库中获取一条数据,然后更改了一些内容,之后就保存记录到数据库中,那么系统就应该只保存用户之前取出的那条数据。为了达到这个效果,我们可以在每次数据更新的时候添加一个datetime类型或者int类型的字段来标记。当我们从数据库中返回一条记录时,我们在UI显示层显示出来,然后修改数据,再保存到数据库中,那么我们在数据库中的存储过程的SQL语句就只是更新之前我们取出的那条数据。在Sql Server 2005中,我们可以用timespan类型的字段来跟踪和标识每条数据的版本,所以sql语句如下:
请大家注意LastUpdateDate 字段其实就是一个标识每条数据版本的辅助字段。
调用的C#代码如下:
下面我们就来看看数据在DAL和BLL之间是以什么形式来交换的,一般有以下选择:
DataSet
类型化的DataSet
自定义实体
当BLL类调用从DAL中的一些方法拉获取数据时,它们将怎样接受这些数据?
是用DataSet/DataTable,还是自定义实体?
下面我们就看看给自的优缺点:
如果选择使用DataSet/DataTable在DAL和BLL层之前传递数据,就需要使用ADO.NET中的方法来访问BLL中的数据。如果选择自定义实体,那么所有的数据将被封装到自定义实体类和类的集合中,这样就可以根据具体情况来访问BLL,这中方式更加的自然。
很多人认为DataSet/DataTable对于基于桌面的只能客户端程序来说是最好的选择,但是对于可扩展的高性能Web网站来说,则不够强大。这里所说的DataSet是指类型化的DataSet,因为非类型化的DataSet有很多的确定:进行配置和编码时,很容易把表名,字段名,关系名,或者字段的类型搞错,而且在调试的时候花费很多的时间。而类型化的DataSet很容易使用,因为它可以使用智能感应来获得字段名,而且还内置了排序和过滤的功能,执行数据绑定而且DataSet/DataTable还是可以序列化的,这样就可以更加容易的传输它们。
DataSet/DataTable的缺点:性能和扩展性的局限性,数据的表示形式和业务规则验证。如果只需要传递一条数据,那么我们仍然需要创建一个完整的DataSet/DataTable,这就需要系统开销。而且DataSet/DataTable和数据库的关于很密切,可以说是内存中的数据库,所以DataSet/DataTable没有一个清晰的,面向对象的数据表示方式。尽管DataSet/DataTable同IDE集成的很好,但是每次数据库结构发生变化,如添加字段,重命名等,那么我们就得重新创建类型化的DataSet.最大的问题就是:在DataSet中添加自定义的业务和验证逻辑困难。在将它们保存到数据库之前,或者在运行其他语句之前要对那些新记录强制之心业务规则,需要写很多的代码,而且需要深入的了解ADO.NET的知识。大家要明白 如果使用的是自定义的实体,那么我们就需要手动的把数据库中的表映射为自定义的业务类,而且这将是一项麻烦的事情,而且还有一点很之前的DataSet/DataTable一样:如果数据库中的表结构变化,那么我们又得重新修改我们的自定义的业务类,
我们真的就没有办法了吗?
有,绝对有方法可以解决上面的问题,那么就Linq to Sql 和它的ORM设计器。
下面我们就看看Linq to Sql
注:在本章中,不需要大家对Linq很熟悉,只要了解就行了,因为对与用到的知识,会详细的讲述的。而且大家一定要一步步的跟着做,切记!
在VS2008中,一个改进的功能就是Linq还有就是对象关系模型(ORM)设计器,ORM设计器可以自动的生成类,并且自动进行ADO.NET的操作。其中一个最中要的类就是DataContext,大家可以把这个类和我们之前谈的SqlHelper类来类比,它们都是封装了数据操作的细节。
下面请大家跟着我一起来做:
1.创建一个新的Sql Server 2005 数据库,或者直接用VS2008自带的SQL Express创建也行,如下:
2.数据库名称为HRPaidTimeOff
3.创建ENTUserAccount表,如下:
我们在这个项目中的所有表都以ENT为前缀,表明这个表是可重用企业级框架的一部分。还有ENTUserAccountId是identity的。
来看看表的定义和字段的意义:这个表其实就是一个用户账户表,其中ENTUserAccountId就是用户账户的ID,也是主键,WindowsAccountName就是用户的计算机名称,FirstName,LastName就是用户名字,Email用户邮箱,IsActive表明这个用户是否是激活状态,后面的IsertDate,InsertENTUserAccountId,UpdateDate,UpdateENTUserAccountId,
这几个字段在我们项目的所有的表中都有的,因为我们之后要添加审计和跟踪功能要用到这些字段,它们的意义分别是:用户添加的时间,是哪个用户添加了当前的这个用户,用户更新时间,是哪个用户更新的当前用户。举个例子就是:加入Mary是管理员,她添加了一个Bob用户,那么ENTUserAccountId就是Bob用户记录的主键,FirstName就是Bob,InsertENTUserAccountId就是Mary的ID。
下面我们就添加一条记录:
WindowsAccountName = Lufy
FirstName = Yang
LastName = Wang
Email = yangyang4502@yahoo.com.cn
IsActive = True
InsertDate=getdate()
InsertENTUserAccountId = 1
UpdateDate = getdate()
UpdateENTUserAccountId = 1
现在数据表就创建好了,打开VS2008创建连接(如果我们之前是直接用VS2008的服务器资源管理创建的,那么下面的步骤就不用作了,如果使用的 sql 2005,下面的步骤就要做
1.在VS2008中选择"视图"-"服务器资源管理"
2.点击"数据库连接",右击,"添加连接",数据库选择"Microsoft SQL Server (SqlClient)",服务器为".\sqlexpress".
3.选择身份验证方式为"Windows验证",选择数据库为"HRPaidTimeOff"
4.测试连接,然后OK。