本文将以设计和实现紧密结合的方式来分析,这也是我们广大实践型的软件开发人员的风格。先看一下设计图和具体实现vs.net工程的表格。
mspetshop 3.0 系统结构图:

从图中可以看到系统大体分为presentation,business logic,data access 三层,每层中又有子层。每层(也包括子层)各司其职,又互相协作,本文顺序以此图为准,从下到上分析。
对应上图,具体的.net project实现列表(借用ms文章中的列表不用翻译了吧)
|
project |
purpose |
|
bll |
home for business logic components |
|
configtool |
administration application used to encrypt connection strings and create event log source |
|
dalfactory |
classes used to determine which database access assembly to load |
|
idal |
set of interfaces which need to be implemented by each dal implementation |
|
model |
thin data classes or business entities |
|
oracledal |
oracle specific implementation of the pet shop dal which uses the idal interfaces |
|
post-build |
project to run post compile actions such as adding assemblies to the gac or com+ |
|
pre-build |
project to remove assemblies from the gac or unregister assemblies from com+ |
|
sqlserverdal |
microsoft sql server specific implementation of the pet shop dal which uses the idal interfaces |
|
utility |
set of helper classes including a wrapper for the dpapi |
|
web |
web pages and controls |
|
solution items |
miscellaneous items used to build the application such as pet shop.snk key file used to sign application assemblies |
另外我写这篇文章时是一边看源码一边写,所以建意大家最好安装一个petshop3,因为时间仓促,水平有限,如我有不对之处请给我发email更正。email:cocoboy79@163.com qq:364941
首先我们来看一下dal层。
一:data access layer:
1 petshop.utility如下图:(上表中utility为其实现工程)

正如上表所描述,这个名字空间有两个类一个是connectioninfo用于加密解密数据库连接信息,另一个dataprotector调用了crypt32.dll和kernel32.dll实现一些底层数据安全操作,这个类要在下面的petshop.xxxdal名字空间中调用,可见petshop.utility只是起到的是数据访问辅助工具的作用。
2 petshop.sqlserverdal ――系统结构图中dal层中的sqlserver dal子层实现

sqlhelper类实际上是封装了关于此系统中数据库操作访问的一些常用功能,其中它还会调用上面的petshop.utility中的conectioninfo类方法加密解密连接字符串,如:connectioninfo.decryptdbconnectionstring方法。sqlhelper类是基于microsoft data access application block for .net。这个东西是用来帮助用户更好的在.net的访问数据。如ms一段话:are you involved in the design and development of data access code for .net-based applications? have you ever felt that you write the same data access code again and again? have you wrapped data access code in helper functions that let you call a stored procedure in one line? if so, the microsoft® data access application block for .net is for you。其实可以自已写一个类似sqlhelper的东西,以实现一般化的对数据库的操作,以在各项目中重用,当然也可以使用现在的ms为你做好的这个sqlhelper或是microsoft® data access application block for .net,避免不同项目中总是写同样的重复的数据库访问程序。有时间最好还是看一下sqlhelper的具体程序实现思路以及所提到的那个microsoft data access application block for .net。不过这里我们的sqlhelper应该只是部分实现。更全面信息请参看:http://msdn.microsoft.com/library/en-us/dnbda/html/daab-rm.asp
account类对用户帐户进行操作如insert,update,signin,其中这些对数据库的操作,使用了上面的sqlhelper类来实现。另外inventory和order,product,profile和account类的都是同样对数据库相关表进行操作,程序风格一致,这些类中对数据库的操作都是通过此名字空间下的sqlhelper类进行的,例如,下面语句:
private const string sql_insert_signon = "insert into signon values (@userid, @password)";
private const string parm_user_id = "@userid";
private const string parm_password = "@password";
来定义一个sql语句,以及声明其中可变参数,然后像下面这样用sqlhelper类的合适的方法执行:
sqlhelper.executenonquery(trans, commandtype.text, sql_insert_signon, signonparms);
最后在sqlhelper.executenonquery实现中,再调用ado.net中的相关类最终执行对数据库的操作,可见sqlhelper在这里又封装了一下ado.net相关类以优化数据操作。正如sqlhelper.cs中注释提示:the sqlhelper class is intended to encapsulate high performance, scalable best practices for common uses of sqlclient.下面是sqlhelper. executenonquery的实现内容:
public static int executenonquery(string connstring, commandtype cmdtype, string cmdtext, params sqlparameter[] cmdparms) {
//注:运行时cmdtext的实参就是sql_insert_signon
sqlcommand cmd = new sqlcommand();
using (sqlconnection conn = new sqlconnection(connstring)) {
preparecommand(cmd, conn, null, cmdtype, cmdtext, cmdparms);
int val = cmd.executenonquery();
cmd.parameters.clear();
return val;
}
}
另外inventory和order,product,profile和account类的声明都是像public class account : iaccount这样实现某个相关的接口,像iaccount这样的接口是在petshop.idal中声明的,见后面介绍。
3 petshop.oracledal ―――系统结构图中 dal层的oracledal子层实现

个人认为结构应该同上面的petshop. sqlserverdal,另外sqlhelper变成了orahelper,在orahelper中当然具体实现了对特定的oracle数据库的联接操作,看一下源程序很明显原来的 sqlcommand cmd = new sqlcommand(); 变成了oraclecommand cmd = new oraclecommand();。
注意一下:在系统结构图中的dal层还有两个xxx daab的子层,它们对应的实现在哪里呢? 下面对应一下:
以下是左边是图中 dataaccesslayer的各部分,右边是具体实现所在名字空间或类
sqlserver dal――petshop.sqlserverdal名字空间
sql daab――petshop.sqlserverdal.sqlhelper类
oracle dal――petshop.oracledal名字空间
oracle daab――petshop.oracledal.orahelper类
4 petshop.idal 数据访问接口――对应系统结构图中dal interface

接口是一种系列‘功能’的声明或名单,接口没有实现细节,如下接口iaccount定义也可以看出iaccount只有声明:
using system;
using petshop.model;
namespace petshop.idal
{
// inteface for the account dal
public interface iaccount
{
// authenticate a user
accountinfo signin(string userid, string password);
/// get a users address stored in the database
addressinfo getaddress(string userid);
/// insert an account into the database
void insert(accountinfo account);
/// update an account in the database
void update(accountinfo account);
}
}
您只需要调用接口,而不用管接口是如何实现的那么接口没有实现,调用它有什么用?实际上接口的实现是由某个类来做的,那么这里的iaccount接口是由petshop.sqlserverdal.account类或是petshop.oracledal.account类来实现的,从他们的定义可以看到:
public class account : iaccount {…….}
为什么是两个类都实现同一接口又是‘或’呢?因为这里使用接口的目的就是为了统一‘外观’,当上层bll层调用此接口方法时不用知道这个接口由哪个类实现的。那谁来确定使用哪个类的实现?请再看下面。
(petshop.idal下的其它接口和iaccount一样,故在此略过。)
5 petshop.dalfactory 数据访问工厂

工厂模式是设计模式的一种,以我理解就像factory这个词一样,对于用户来说,工厂里产品如何生产的你不用知道,你只要去用工厂里生产出来的东西就可以了。mspetshop3.0用工厂模式来实现了对sqlserver和oracle数据库访问的操作,而用户(business logic layer)不用知道也不用关心后台用的是哪一种数据库,它只要用接口就行了,接口中定义了要用的方法,当调用接口时会根据具体的情况再去调用底层数据访问操作。而现在这个dalfactory就是关键,当bll层要操作数据库时,dalfactory会根据具体情况再去使用本文上面介绍的sqlserverdal和oracledal中的一个。这样系统上层只管调用,而下层来实现细节,上级只管发号施令,下级去干活。对于上层来说实现细节被隐藏了。
那么dalfactory是如何决定应该用sqlserverdal还是用oracledal的呢?我们接着分析。
以下是petshop.dalfactory.account类的实现:
namespace petshop.dalfactory {
/// <summary>
/// factory implementaion for the account dal object
/// </summary>
public class account
{
public static petshop.idal.iaccount create() //<<<<ß----这里返回接口
{
/// look up the dal implementation we should be using
string path = system.configuration.configurationsettings.appsettings["webdal"];
string classname = path + ".account";
// using the evidence given in the config file load the appropriate assembly and class
return (petshop.idal.iaccount) assembly.load(path).createinstance(classname);
}
}
}
以下则是web.config中<appsettings>节点中的一部分:
<add key="webdal" value="petshop.sqlserverdal" />
<add key="ordersdal" value="petshop.sqlserverdal" />
<add key="event log source" value=".net pet shop" />
上面的create()方法返回iaccount接口,用system.configuration.configurationsettings.appsettings["webdal"];则可以得到web.config的<appsettings>节点中的关于系统中应该使用哪个数据访问层(sqlserverdal还是oracledal)的信息。因为我在安装petshop3.0时选择的是sqlserver所以在此是:value="petshop.sqlserverdal",如果用的是oracle那就是value="petshop.oracledal" 了吧!而且这个文件也应该是可以更改的。接下来classname=path+”.account”返回的应该是petshop.sqlserverdal.account,然后再用assembly.load加载petshop.sqlserverdal.dll,同时创建petshop.sqlserverdal.account的实例,并以接口(petshop.idal.iaccount)类型返回。这样bll调用iaccount接口时就会用petshop.sqlserverdal.account类的实现代码。(回上面第4再看一下)
看!这样根据系统当前web.config文件的配置描述(这也应该是系统运行时实际的配置),bll层只要像下面这样:
// get an instance of the account dal using the dalfactory
iaccount dal = petshop.dalfactory.account.create();
accountinfo account = dal.signin(userid, password);//<<ß----看看上面第4点的iaccount接口
就可以直接调用接口方法通过下层dal层操作数据库了(在此具体为用户账号相关操作),而bll层并不用知道应该通过sqlserverdal还是oracledal访问数据库,这由都dal factory决定,你用的是什么数据库以及底层细节,更不用bll知道,这样做的好处是对于bll层以及更上层的程序不会或很少机率会因为底层程序变动影响,因为bll层中调用接口就行了,只要那个接口定义没变,一切仍然ok.
6 petshop.configtool

首先在..\microsoft\petshop\configtool\中有一个app.config文件,看一下其中内容,分别定义了两种数据库的联接字符串,在app.config中有一行 <add key="webconfigfilelocation" value="web\web.config" /> 则标识出给asp.net程序使用的web.config配置文件的相对位置。然后看一下petshopconnectionstring的encryptconnectionstring方法的源码,这个类中先是从当前目录的app.config文件中读出web.config文件的位置,如下:
public static readonly string configfile = configurationsettings.appsettings["webconfigfilelocation"];
然后语句
xmldocument doc = new xmldocument();
doc.load(configfile);
加载web.config文件,最后将加密的连接字符串写入web.config对应的xml节点中。以供asp.net应用程序使用。其中的加密还是使用petshop.utility。
而configconsole,调用petshopconnectionstring类encryptconnectionstring执行对联接字符串进行加密。另外petshopeventlog类也是在configconsole中使用的。用于记录程序日志。
所以在最后部署时web.config的连接字符串是加密的。
7 petshop.model 业务实体模型
../uploadfile/200611/20061130145647929.bmp
这个本来想在分析bll层时再说,但是在sqlserverdal和oracledal中都使用了这些model,无论怎么样,上层的程序执行最终结果都是要操作数据库,而数据库是关系型,不是面向对象的,那就得把平面的‘表’结合业务规则抽象成类,这样想办法让上层(bll及以上)以为自已在操作类而不是数据库表,从而使‘它们’感觉没有数据库的存在,上层只管面向对象编程就可以了。类似现在所说的o-r mapping,但o-r mapping比这种简单的数据到对象的持久化要复杂。据说可以在表结构有变化的情况下,上层应用程序代码不用更改,只要改o-r mapping的相关设置就可以了。
上面第2节中已看到petshop的sqlserverdal和oracledal中定义的sql语句,然后根据上层的调用,把sql语句传给sqlhelper执行,再来看看sqlserverdal的一段程序:
private void setaccountparameters(sqlparameter[] parms, accountinfo acc) {
parms[0].value = acc.email;
parms[1].value = acc.address.firstname;
parms[2].value = acc.address.lastname;
parms[3].value = acc.address.address1;
parms[4].value = acc.address.address2;
parms[5].value = acc.address.city;
parms[6].value = acc.address.state;
parms[7].value = acc.address.zip;
parms[8].value = acc.address.country;
parms[9].value = acc.address.phone;
parms[10].value = acc.userid;
}
parms[x]就是那些有声明为常量的有参数的sql语句中的参数,这里用model中的accountinfo的各field和这些参数mapping。所以对于bll层,只知道这些model就可以了,反正最后在sqlserverdal或是oracledal中model的成员们会mapping到参数中以存取数据库(为什么不直接mapping到数据集的字段呢?我想应该是因为这里数据库操作直接给sqlheper的原因,不然就没必要用sqlhelper了。于是才又多了图中的xxx daab层,这样mapping给参数再传给daab来处理)
data access layer总结:
dal完成数据库访问任务,上层(bll)直需调用接口即可,不用知道具体访问细节,用factory模式来实现,使用运行时读取web.config的方法来得到连接配置信息,最后选用sqlserverdal或oracledal之一的相对具体子层,同时使用sqlhelper或orahelper之一来完成数据库操作。
文章整理:西部数码--专业提供域名注册、虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!


