.项目概述与架构分析
微软刚推出了基于ASP.NET 2.0下的Pet Shop 4, 该版本有了一个全新的用户界面。是研究ASP.NET 2.0的好范例啊,大家都知道,一直以来,在.NET和Java之间争论不休,到底使用哪个平台开发的企业级应用性能最好、结构最优、生产力最高。为了用事实说话,通过对项目各方面的性能评估进而在比较.NET和Java的高下。用户做比较的这个项目就是Petshop。正因为Petshop肩负着上面所说的重任,各方面必须是最优的,架构设计应该是经过慎重考虑的。所以其一经推出,便成为了开发者、架构师等人学习、研究的典范。
日前微软推出了基于.NET Framework 2.0开发的Petshop 4。新的Petshop4实现了与Petshop 3相同甚至更多的特性,虽然采用了Master Pages,Membership,以及Profile,SqlCacheDependency,但是代码量却减少了四分之一。同时,在事务、数据缓存、安全方面使用了.NET 2.0附带的特性,构建了一个灵活的最佳实践的应用程序。
他们利用了Project Conversion Wizard把项目从ASP.NET 1.1移植到了ASP.NET 2.0,然后做了以下改动:
1.用System.Transactions代替了原来的Serviced Components提供的事务功能
代码实现:PetShop.BLL.OrderSynchronous 的 public void Insert(PetShop.Model.OrderInfo order)。
2.用强类型的范型集合代替了原来的弱类型集合
public IList<ProductInfo> GetProductsByCategory(string category)
{
// Return new if the string is empty
if (string.IsNullOrEmpty(category))
return new List<ProductInfo>();
// Run a search against the data store
return dal.GetProductsByCategory(category);
}
3.采用ASP.NET 2.0 Membership来做认证和授权
4.创建了针对Oracle 10g的Custom ASP.NET 2.0 Membership Provider
5.利用ASP.NET 2.0的Custom Oracle 和 SQL Server Profile Providers 做用户状态管理,包括购物车等
6.采用了Master Pages,取代了原来的用户控件,来实现统一的界面效果
7.使用了ASP.NET 2.0 Wizard控件实现check-out
8.使用了SqlCacheDependency来实现数据库层次的缓存更新(cache invalidation)功能
9.使用了消息队列来实现异时订单处理。
4.0版本相对于3.0版本代码量的变化:(总体代码减少了25%)
|
v3 |
v4 |
Presentation Layer |
1,822 |
1,365 |
Model |
349 |
395 |
Business Logic Layer |
210 |
199 |
Data Access Layer |
1,538 |
985 |
Total Lines of Code |
3,919 |
2,944 |
1) 使用Wizard组件实现sign-in and check-out steps
2)使用Master Page减少了HTML代码
3)使用Membership进行权限处理
同时在4.0版本中新增加的代码:
Custom Profile |
853 |
Oracle Membership |
586 |
Cache Dependency |
90 |
Message Queue |
147 |
Total Lines of Code |
1,676 |
2.整体架构:
项目列表:从整体可以看出,Pet Shop 4的项目体系已经很庞大,考虑的方面也较3.0更全面复杂。
PetShop 4.0 设计研究——petShop 4.0 的命名空间 以及各个项目模块的说明
序号 |
项目名称 |
程序集名称Assembly Name |
默认命名空间Default Namespace |
描述 |
1 |
WEB |
表示层 | ||
2 |
Model |
PetShop.Model |
PetShop.Model |
业务实体 |
3 |
BLL |
PetShop.BLL |
PetShop.BLL |
业务逻辑层 |
4 |
DALFactory |
PetShop.DAL |
PetShop.DALFactory |
数据层的抽象工厂 |
5 |
IDAL |
PetShop.IDAL |
PetShop.IDAL |
数据访问层接口定义 |
6 |
SQLServerDAL |
PetShop.SQLServerDAL |
PetShop.SQLServerDAL |
SQLServer数据访问层 |
7 |
OracleDAL |
PetShop.OracleDAL |
PetShop.OracleDAL |
Oracle数据访问层 |
8 |
DBUtility |
PetShop.DBUtility |
PetShop.DBUtility |
数据库访问组件基础类 |
9 |
CacheDependencyFactory |
PetShop.CacheDependencyFactory |
PetShop.CacheDependencyFactory |
缓存依赖类的工厂类 |
10 |
ICacheDependency |
PetShop.ICacheDependency |
PetShop.ICacheDependency |
缓存依赖类接口 |
11 |
TableCacheDependency |
PetShop.TableCacheDependency |
PetShop.TableCacheDependency |
缓存依赖实现类 |
12 |
IBLLStrategy |
PetShop.IBLLStrategy |
PetShop.IBLLStrategy |
同步/异步处理策略接口(实现在bll根据配置反射选择) |
13 |
MessagingFactory |
PetShop.MessagingFactory |
PetShop.MessagingFactory |
异时处理消息队列的抽象工厂 |
14 |
IMessaging |
PetShop.IMessaging |
PetShop.IMessaging |
异时处理消息队列接口定义 |
15 |
MSMQMessaging |
PetShop.MsmqMessaging |
PetShop.MsmqMessaging |
异时处理消息队列的实现 |
16 |
Profile |
PetShop.Profile |
PetShop.Profile |
Profile的数据访问层 |
17 |
ProfileDALFactory |
PetShop.ProfileDALFactory |
PetShop.ProfileDALFactory |
ProfileDAL的工厂类(反射创建ProfileDAL) |
18 |
IProfileDAL |
PetShop.IProfileDAL |
PetShop.IProfileDAL |
Profile的数据访问层接口定义 |
19 |
OracleProfileDAL |
PetShop.OracleProfileDAL |
PetShop.OracleProfileDAL |
Oracle的Profile Providers 做用户状态管理 |
20 |
SQLProfileDAL |
PetShop.SQLProfileDAL |
PetShop.SQLProfileDAL |
SQL Server 的Profile Providers 做用户状态管理 |
21 |
Membership |
PetShop.Membership |
PetShop.Membership |
Membership认证和授权管理 |
22 |
OrderProcessor |
PetShop.OrderProcessor |
PetShop.OrderProcessor |
后台处理进程,处理订单队列 |
序号 |
项目名称 |
描述 |
1 |
BLL |
业务逻辑层 |
2 |
CacheDependencyFactory |
缓存依赖类的工厂类 |
3 |
WEB |
表示层 |
4 |
DALFactory |
数据层的抽象工厂 |
5 |
DBUtility |
数据访问类组件 |
6 |
IBLLStrategy |
同步/异步策略接口 |
7 |
ICacheDependency |
缓存依赖类接口 |
8 |
IDAL |
数据访问层接口定义 |
9 |
IMessaging |
异时处理消息队列接口定义 |
10 |
IProfileDAL |
Profile的数据访问层接口定义 |
11 |
Membership |
Membership认证和授权管理 |
12 |
MessagingFactory |
异时处理消息队列的抽象工厂 |
13 |
Model |
业务实体 |
14 |
MSMQMessaging |
异时处理消息队列的实现 |
15 |
OracleDAL |
Oracle数据访问层 |
16 |
OracleProfileDAL |
Oracle的Profile Providers 做用户状态管理,包括购物车等 |
17 |
OrderProcessor |
后台处理进程,处理订单队列 |
18 |
Profile |
Profile的数据访问层 |
19 |
ProfileDALFactory |
ProfileDAL的工厂类(反射创建ProfileDAL) |
20 |
SQLProfileDAL |
SQL Server 的Profile Providers 做用户状态管理,包括购物车等 |
21 |
SQLServerDAL |
SQLServer数据访问层 |
22 |
TableCacheDependency |
缓存依赖实现类 |
项目分解:
由于整体已经有22个项目,所以,对于初学者一看就晕了,所以,我做了分解,可以大体上分几块去理解。
序号 |
项目名称 |
描述 |
1 |
WEB |
表示层 |
2 |
Model |
业务实体 |
3 |
BLL |
业务逻辑层 |
4 |
DALFactory |
数据层的抽象工厂 |
5 |
IDAL |
数据访问层接口定义 |
6 |
SQLServerDAL |
SQLServer数据访问层 |
7 |
OracleDAL |
Oracle数据访问层 |
8 |
DBUtility |
数据库访问组件基础类 |
9 |
CacheDependencyFactory |
缓存依赖类的工厂类 |
10 |
ICacheDependency |
缓存依赖类接口 |
11 |
TableCacheDependency |
缓存依赖实现类 |
12 |
IBLLStrategy |
同步/异步处理策略接口(实现在bll根据配置反射选择) |
13 |
MessagingFactory |
异时处理消息队列的抽象工厂 |
14 |
IMessaging |
异时处理消息队列接口定义 |
15 |
MSMQMessaging |
异时处理消息队列的实现 |
16 |
Profile |
Profile的数据访问层 |
17 |
ProfileDALFactory |
ProfileDAL的工厂类(反射创建ProfileDAL) |
18 |
IProfileDAL |
Profile的数据访问层接口定义 |
19 |
OracleProfileDAL |
Oracle的Profile Providers 做用户状态管理 |
20 |
SQLProfileDAL |
SQL Server 的Profile Providers 做用户状态管理 |
21 |
Membership |
Membership认证和授权管理 |
22 |
OrderProcessor |
后台处理进程,处理订单队列 |
3.Petshop 4中的设计模式:
工厂模式:
首当其冲的就是工厂模式,很容易就可以看出来,也是应用最多的。
DALFactory:数据访问层的抽象工厂(决定创建哪种数据库类型的数据访问层。可以选择:SQLServer,Oracle)
CacheDependencyFactory:缓存依赖类的工厂类。(创建具体表的缓存依赖)
MessagingFactory :异时处理消息队列的抽象工厂(反射创建具体的异时处理类)
ProfileDALFactory:ProfileDAL的工厂类(反射选择创建Oracle 和SQL Server的 ProfileDAL)
策略模式: IorderStrategy
中介模式
CategoryDataProxy ItemDataProxy ProductDataProxy
4.数据库
Pet Shop Orders Database
Pet Shop Inventory Database
The Profile database is used to store user specific information, such as account info and shopping cart contents. The database has the following overall structure of tables:
Pet Shop Profile Database
参考原文(ltp):http://ltp.cnblogs.com/archive/2006/04/17/377564.html
具体介绍可以参看MSDN:
.NET Pet Shop 4: Migrating an ASP.NET 1.1 Application to 2.0
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/bdasamppet4.asp
下载:
二、PetShop数据访问层之数据库访问设计
在系列一中,我从整体上分析了PetShop的架构设计,并提及了分层的概念。从本部分开始,我将依次对各层进行代码级的分析,以求获得更加细致而深入的理解。在PetShop 4.0中,由于引入了ASP.Net 2.0的一些新特色,所以数据层的内容也更加的广泛和复杂,包括:数据库访问、Messaging、MemberShip、Profile四部分。在系列二中,我将介绍有关数据库访问的设计。
在PetShop中,系统需要处理的数据库对象分为两类:一是数据实体,对应数据库中相应的数据表。它们没有行为,仅用于表现对象的数据。这些实体类都被放到Model程序集中,例如数据表Order对应的实体类OrderInfo,其类图如下:
这些对象并不具有持久化的功能,简单地说,它们是作为数据的载体,便于业务逻辑针对相应数据表进行读/写操作。虽然这些类的属性分别映射了数据表的列,而每一个对象实例也恰恰对应于数据表的每一行,但这些实体类却并不具备对应的数据库访问能力。
由于数据访问层和业务逻辑层都将对这些数据实体进行操作,因此程序集Model会被这两层的模块所引用。
第二类数据库对象则是数据的业务逻辑对象。这里所指的业务逻辑,并非业务逻辑层意义上的领域(domain)业务逻辑(从这个意义上,我更倾向于将业务逻辑层称为“领域逻辑层”),一般意义上说,这些业务逻辑即为基本的数据库操作,包括Select,Insert,Update和Delete。由于这些业务逻辑对象,仅具有行为而与数据无关,因此它们均被抽象为一个单独的接口模块IDAL,例如数据表Order对应的接口IOrder:
将数据实体与相关的数据库操作分离出来,符合面向对象的精神。首先,它体现了“职责分离”的原则。将数据实体与其行为分开,使得两者之间依赖减弱,当数据行为发生改变时,并不影响Model模块中的数据实体对象,避免了因一个类职责过多、过大,从而导致该类的引用者发生“灾难性”的影响。其次,它体现了“抽象”的精神,或者说是“面向接口编程”的最佳体现。抽象的接口模块IDAL,与具体的数据库访问实现完全隔离。这种与实现无关的设计,保证了系统的可扩展性,同时也保证了数据库的可移植性。在PetShop中,可以支持SQL Server和Oracle,那么它们具体的实现就分别放在两个不同的模块SQLServerDAL、OracleDAL中。
以Order为例,在SQLServerDAL、OracleDAL两个模块中,有不同的实现,但它们同时又都实现了IOrder接口,如图:
从数据库的实现来看,PetShop体现出了没有ORM框架的臃肿与丑陋。由于要对数据表进行Insert和Select操作,以SQL Server为例,就使用了SqlCommand,SqlParameter,SqlDataReader等对象,以完成这些操作。尤其复杂的是Parameter的传递,在PetShop中,使用了大量的字符串常量来保存参数的名称。此外,PetShop还专门为SQL Server和Oracle提供了抽象的Helper类,包装了一些常用的操作,如ExecuteNonQuery、ExecuteReader等方法。
在没有ORM的情况下,使用Helper类是一个比较好的策略,利用它来完成数据库基本操作的封装,可以减少很多和数据库操作有关的代码,这体现了对象复用的原则。PetShop将这些Helper类统一放到DBUtility模块中,不同数据库的Helper类暴露的方法基本相同,只除了一些特殊的要求,例如Oracle中处理bool类型的方式就和SQL Server不同,从而专门提供了OraBit和OraBool方法。此外,Helper类中的方法均为static方法,以利于调用。OracleHelper的类图如下:
对于数据访问层来说,最头疼的是SQL语句的处理。在早期的CS结构中,由于未采用三层式架构设计,数据访问层和业务逻辑层是紧密糅合在一起的,因此,SQL语句遍布与系统的每一个角落。这给程序的维护带来极大的困难。此外,由于Oracle使用的是PL-SQL,而SQL Server和Sybase等使用的是T-SQL,两者虽然都遵循了标准SQL的语法,但在很多细节上仍有区别,如果将SQL语句大量的使用到程序中,无疑为可能的数据库移植也带来了困难。
最好的方法是采用存储过程。这种方法使得程序更加整洁,此外,由于存储过程可以以数据库脚本的形式存在,也便于移植和修改。但这种方式仍然有缺陷。一是存储过程的测试相对困难。虽然有相应的调试工具,但比起对代码的调试而言,仍然比较复杂且不方便。二是对系统的更新带来障碍。如果数据库访问是由程序完成,在.Net平台下,我们仅需要在修改程序后,将重新编译的程序集xcopy到部署的服务器上即可。如果使用了存储过程,出于安全的考虑,必须有专门的DBA重新运行存储过程的脚本,部署的方式受到了限制。
我曾经在一个项目中,利用一个专门的表来存放SQL语句。如要使用相关的SQL语句,就利用关键字搜索获得对应语句。这种做法近似于存储过程的调用,但却避免了部署上的问题。然而这种方式却在性能上无法得到保证。它仅适合于SQL语句较少的场景。不过,利用良好的设计,我们可以为各种业务提供不同的表来存放SQL语句。同样的道理,这些SQL语句也可以存放到XML文件中,更有利于系统的扩展或修改。不过前提是,我们需要为它提供专门的SQL语句管理工具。
SQL语句的使用无法避免,如何更好的应用SQL语句也无定论,但有一个原则值得我们遵守,就是“应该尽量让SQL语句尽存在于数据访问层的具体实现中”。
当然,如果应用ORM,那么一切就变得不同了。因为ORM框架已经为数据访问提供了基本的Select,Insert,Update和Delete操作了。例如在NHibernate中,我们可以直接调用ISession对象的Save方法,来Insert(或者说是Create)一个数据实体对象:
public void Insert(OrderInfo order)
{
ISession s = Sessions.GetSession();
ITransaction trans = null;
try
{
trans = s.BeginTransaction();
s.Save( order);
trans.Commit();
}
finally
{
s.Close();
}
}
没有SQL语句,也没有那些烦人的Parameters,甚至不需要专门去考虑事务。此外,这样的设计,也是与数据库无关的,NHibernate可以通过Dialect(方言)的机制支持不同的数据库。唯一要做的是,我们需要为OrderInfo定义hbm文件。
当然,ORM框架并非是万能的,面对纷繁复杂的业务逻辑,它并不能完全消灭SQL语句,以及替代复杂的数据库访问逻辑,但它却很好的体现了“80/20(或90/10)法则”(也被称为“帕累托法则”),也就是说:花比较少(10%-20%)的力气就可以解决大部分(80%-90%)的问题,而要解决剩下的少部分问题则需要多得多的努力。至少,那些在数据访问层中占据了绝大部分的CRUD操作,通过利用ORM框架,我们就仅需要付出极少数时间和精力来解决它们了。这无疑缩短了整个项目开发的周期。
还是回到对PetShop的讨论上来。现在我们已经有了数据实体,数据对象的抽象接口和实现,可以说有关数据库访问的主体就已经完成了。留待我们的还有两个问题需要解决:
1、数据对象创建的管理
2、利于数据库的移植
在PetShop中,要创建的数据对象包括Order,Product,Category,Inventory,Item。在前面的设计中,这些对象已经被抽象为对应的接口,而其实现则根据数据库的不同而有所不同。也就是说,创建的对象有多种类别,而每种类别又有不同的实现,这是典型的抽象工厂模式的应用场景。而上面所述的两个问题,也都可以通过抽象工厂模式来解决。标准的抽象工厂模式类图如下:
例如,创建SQL Server的Order对象如下:
PetShopFactory factory = new SQLServerFactory();
IOrder = factory.CreateOrder();
要考虑到数据库的可移植性,则factory必须作为一个全局变量,并在主程序运行时被实例化。但这样的设计虽然已经达到了“封装变化”的目的,但在创建PetShopFactory对象时,仍不可避免的出现了具体的类SQLServerFactory,也即是说,程序在这个层面上产生了与SQLServerFactory的强依赖。一旦整个系统要求支持Oracle,那么还需要修改这行代码为:
PetShopFactory factory = new OracleFactory();
修改代码的这种行为显然是不可接受的。解决的办法是“依赖注入”。“依赖注入”的功能通常是用专门的IoC容器提供的,在Java平台下,这样的容器包括Spring,PicoContainer等。而在.Net平台下,最常见的则是Spring.Net。不过,在PetShop系统中,并不需要专门的容器来实现“依赖注入”,简单的做法还是利用配置文件和反射功能来实现。也就是说,我们可以在web.config文件中,配置好具体的Factory对象的完整的类名。然而,当我们利用配置文件和反射功能时,具体工厂的创建就显得有些“画蛇添足”了,我们完全可以在配置文件中,直接指向具体的数据库对象实现类,例如PetShop.SQLServerDAL.IOrder。那么,抽象工厂模式中的相关工厂就可以简化为一个工厂类了,所以我将这种模式称之为“具有简单工厂特质的抽象工厂模式”,其类图如下:
DataAccess类完全取代了前面创建的工厂类体系,它是一个sealed类,其中创建各种数据对象的方法,均为静态方法。之所以能用这个类达到抽象工厂的目的,是因为配置文件和反射的运用,如下的代码片断所示:
public sealed class DataAccess
{
// Look up the DAL implementation we should be using
private static readonly string path = ConfigurationManager.AppSettings[”WebDAL”];
private static readonly string orderPath = ConfigurationManager.AppSettings[”OrdersDAL”];
public static PetShop.IDAL.IOrder CreateOrder()
{
string className = orderPath + “.Order”;
return (PetShop.IDAL.IOrder)Assembly.Load(orderPath).CreateInstance(className);
}
}
在PetShop中,这种依赖配置文件和反射创建对象的方式极其常见,包括IBLLStategy、CacheDependencyFactory等等。这些实现逻辑散布于整个PetShop系统中,在我看来,是可以在此基础上进行重构的。也就是说,我们可以为整个系统提供类似于“Service Locator”的实现:
public static class ServiceLocator
{
private static readonly string dalPath = ConfigurationManager.AppSettings[”WebDAL”];
private static readonly string orderPath = ConfigurationManager.AppSettings[”OrdersDAL”];
//……
private static readonly string orderStategyPath = ConfigurationManager.AppSettings[”OrderStrategyAssembly”];
public static object LocateDALObject(string className)
{
string fullPath = dalPath + “.” + className;
return Assembly.Load(dalPath).CreateInstance(fullPath);
}
public static object LocateDALOrderObject(string className)
{
string fullPath = orderPath + “.” + className;
return Assembly.Load(orderPath).CreateInstance(fullPath);
}
public static object LocateOrderStrategyObject(string className)
{
string fullPath = orderStategyPath + “.” + className;
return Assembly.Load(orderStategyPath).CreateInstance(fullPath);
}
//……
}
那么和所谓“依赖注入”相关的代码都可以利用ServiceLocator来完成。例如类DataAccess就可以简化为:
public sealed class DataAccess
{
public static PetShop.IDAL.IOrder CreateOrder()
{
return (PetShop.IDAL.IOrder)ServiceLocator. LocateDALOrderObject(”Order”);
}
}
通过ServiceLocator,将所有与配置文件相关的namespace值统一管理起来,这有利于各种动态创建对象的管理和未来的维护。
1.ms介绍
为了简化数据库访问类的使用,我们选择 GoF (译注:指 Erich Gamma 等著《设计模式》一书)概述的工厂设计模式,通过反射动态在运行时加载正确的数据访问对象。 工厂设计模式是这样实现的: 创建一个 C# 接口,其中对于数据库访问类必须公开的每个方法都要声明一个方法。 对于每一个要支持的数据库,都创建一个实现数据库特定代码的具体类,以执行接口也称“协定”中的每一项操作。 为了支持运行时确定加载哪一个具体类,需要创建第三个类,也就是工厂类,它从配置文件中读入一个值以确定应该使用反射加载哪一个程序集。 通过 .NET 的反射命名空间,可以加载某个特定程序集并用该程序集创建某个对象的实例。 为了使应用程序更安全,为版本控制提供更好的支持,我们可以在应用程序配置文件(也就是这里的 web.config. )中添加要加载的程序集文件的“证据”,这意味着 .NET 框架将只加载我们在编译期间签过名而且有正确版本号的程序集。 图 10 说明了业务逻辑类、工厂类和数据库访问类是如何相互操作的。 这一创建的解决方案最重要的优势是数据库访问类可以在业务逻辑类之后编译,只要数据访问类实现了 IDAL 接口。 这意味着,如果要创建应用程序的 DB2 版本,我们不需要改动业务逻辑层(或者 UI 层)。 创建 DB2 兼容版本的步骤如下:
1.创建 DB2 的数据库访问类,它应该实现 IDAL 接口。
2.将 DB2 访问类编译成一个程序集。
3.测试和部署新的数据程序集到一台运行中的服务器上。
4.更改配置文件,指向新的数据库访问类。
无需更改或重新编译业务逻辑组件。
图 10. .NET Pet Shop 中 DAL 工厂类的实现
2.数据分析
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.
3.相关sdk文档资料。
Assembly 类
定义一个 Assembly,它是可重用、无版本冲突并且可自我描述的公共语言运行库应用程序构造块。
有关此类型所有成员的列表,请参阅 Assembly 成员。
Implements IEvidenceFactory, ICustomAttributeProvider, _ ISerializable
public static Assembly Load(string assemblyString);
描述
通过给定程序集的长格式名称加载程序集。
参数
assemblyString
程序集名称的长格式。
返回值
加载的程序集。
public object CreateInstance(string typeName);
描述
使用区分大小写的搜索,从此程序集中查找指定的类型,然后使用系统激活器创建它的实例。
参数
typeName
要查找的类型的 Type.FullName。
返回值
表示该类型的 Object 的实例,其区域性、参数、联编程序和激活属性设置为空引用(Visual Basic 中为 Nothing),并且 BindingFlags 设置为 Public 或 Instance,或者设置为空引用 (Nothing)(如果没有找到 typeName)。
红色——文档重点;蓝色——自己的理解;绿色——不明白的地方
数据库可移植性
这一版本 Microsoft .NET Pet Shop 的关键需求之一是提供支持 Oracle 和 SQL Server 数据库的应用程序实现。 在设计应用程序的数据库访问机制时,我们可以选择应该使用哪一个数据库提供程序;可以使用通用的OLE-DB 托管提供程序或者数据库特定的优化了性能的 .NET 托管提供程序,比如 .NET 框架1.1 中提供的 SQL Server 和 Oracle 托管提供程序。 应用程序的关键需求之一是创建一个高性能的解决方案,因此我们选择用数据库本身的 .NET 托管提供程序构建应用程序。 关于托管提供程序和通用 OLE-DB 提供程序之间的性能差异分析,读者可以参考 Using .NET Framework Data Provider for Oracle to Improve .NET Application Performance,该文档说明了厂商特定的提供程序能够比等价的 OLE-DB 提供程序性能好两到三倍。
在选择数据库特定的访问类时进行的考虑是,我们需要为每个即将支持的数据库平台遍写一个单独的数据访问层,因此应用程序将包含更多代码。 虽然两个数据访问层共享很多公共代码,但还是要明显地分别针对具体数据库。
为了简化数据库访问类的使用,我们选择 GoF概述的“工厂设计模式”,通过反射,动态地在运行时加载正确的数据访问对象。
工厂设计模式是这样实现的: 创建一个 C# 接口,其中对于数据库访问类必须公开(实现?)的每个方法都要声明一个方法(IDAL完成的工作)。 对于每一个要支持的数据库,都创建一个实现数据库特定代码的具体类,以执行接口(也称“协定”)中的每一项操作(DAL完成的工作)。
为了支持运行时确定加载哪一个具体类,需要创建第三个类,也就是工厂类。
注意:DALFactory的具体功能:
功能1:它从配置文件中读入一个值,以确定应该使用“反射”加载的是哪一个程序集
功能2:通过 .NET 的反射命名空间,(可以)加载某个特定(适当的)程序集(SQLServerDAL或OracleDAL),并使用该程序集创建某个对象(如Account类的对象)的实例。
为了使应用程序更安全,为版本控制提供更好的支持,我们可以在应用程序配置文件(也就是这里的 web.config(runtime))中添加要加载的程序集文件的“证据”,这意味着 .NET 框架将只加载我们在编译期间签过名而且有正确版本号的程序集。
图 10 说明了业务逻辑类、工厂类和数据库访问类是如何相互操作的。这一创建的解决方案最重要的优势是数据库访问类可以在业务逻辑类之后编译(???),只要数据访问类实现了IDAL 接口。 这意味着,如果要创建应用程序的 DB2 版本,我们不需要改动业务逻辑层(或者 UI 层)。
创建 DB2 兼容版本的步骤如下:
1.创建 DB2 的数据库访问类,它应该实现 IDAL 接口。
2.将 DB2 访问类编译成一个程序集。
3.测试和部署新的数据库程序集到一台运行中的服务器上。
4.更改配置文件,指向新的数据库访问类。(!!!直接在web.config文件中修改即可!!!)
无需更改或重新编译业务逻辑组件。
图 10. .NET Pet Shop 中 DAL 工厂类的实现
----------------------------------------------------------------------------------
根据上图总结出的访问流程:(修订了SQLServer学习笔记中的访问流程)
1、 通过PetShop.DALFactory.Product.Create()选择加载了一个正确的DAL组件(比如SQLServerDAL)
2、 创建了一个正确的DAL层组件的Product类的实例
3、 返回了一个该Product类在IDAL层的接口IProduct
存在的疑问:
1、 什么不直接返回已经选出来的,适当的DAL组件(如SQLServerDAL)的适当的对象(如Product),而返回了一个该对象在IDAL层的引用?
2、 IDAL层的引进究竟目的何在?是否单纯为了规范下层DAL组件所必须实现的数据访问方法?还是说只要使用工厂模式就必须定义接口,并且即使使用反射确定了应该正确加载的DAL组件的对象,也必须返回对象接口引用?
3、 自己的答案:如果不返回接口,那么上层BLL在得到返回的DAL组件对象之后,就有可能知道在下层究竟使用的是SQLServerDAL还是OracleDAL,这就有可能在BLL层有针对性地进行编程——而这正是使用多层模式想要避免的!!!