解析Microsoft .NET Pet Shop
下面我们会分为几个部份对.NET Pet Shop做个深入的解析,通过这个项目了解Pet Shop的设计模式及架构,这篇文章是第一部份,主要分析Pet Shop在实现多数据库平台移植方面所采用的设计模式和架构。
1.Pet Shop 解决方案
Pet Shop 示例代码的下载地址是:
http://www.microsoft.com/downloads/details.aspx?FamilyId=E2930625-3C7A-49DC-8655-A8205813D6DB&displaylang=en
安装后在你的安装目录中有Pet Shop的解决方案PetShop.sln ,打开解决方案后可以看到解决方案中的各个项目。如下图所示:
图1
Pet Shop 解决方案中的 Visual Studio 项目列表如下:
项目 |
用途 |
BLL |
业务逻辑组件存放之处 |
ConfigTool |
用来加密连接字符串和创建事件日志源的管理应用程序 |
DALFactory |
用来确定加载哪一个数据库访问程序集的类 |
IDAL |
每个 DAL 实现都要实现的一组接口 |
Model |
瘦数据类或业务实体 |
OracleDAL |
Oracle特定的Pet Shop DAL实现,使用了IDAL接口 |
Post-Build |
运行编译后操作的项目,比如将程序集添加到 GAC 或 COM+ |
Pre-Build |
将程序集从 GAC 删除或从 COM+ 注销程序集的项目 |
SQLServerDAL | Microsoft SQL Server特定的Pet Shop DAL实现,使用了IDAL接口 |
Utility |
一组帮助器类,包括 DPAPI 的包装 |
Web |
Web页和控件 |
Solution Items | 用来构建应用程序的杂项,比如用来签署应用程序程序集的Pet Shop.snk密钥文件 |
Pet Shop 3.0 的体系结构如下图所示:
图2:.NET Pet Shop 3.0 体系结构
3.Pet Shop 解决方案中的应用领域
范围 | 用途 | .NET 实现 |
用户接口组件 | 捕获来自用户的数据输入,显示后端系统返回的数据。 它们还处理简单的定位。 参见用户接口组件。 | ASP.NET Web 窗体,用户控件和服务器控件。 这些构造能够清晰地分离设计者的 HTML 和 UI 代码比如按钮的事件处理程序。 |
用户接口处理 | 用后端业务对象控制用户定位和处理流程。 还要处理用户会话数据的管理。 参考用户处理组件。 | 这些是用 C# 类实现的。 会话状态管理由 ASP.NET 处理。 |
业务组件 | 实现应用程序业务逻辑的组件。 | 这些是用 C# 类实现的 |
业务实体 | 在应用程序各层之间传递数据的瘦数据类。 参见业务实体组件。 | 这些是用 C# 类实现的,每个字段都以属性的形式公开。 每个类都标记为“serializable”,启用进程间传输。 |
数据访问层组件 | 处理与后端数据存储区的交互,包括数据库、消息处理系统等。 | 这些组件处理与后端数据存储区的交互,包括数据库、消息处理系统等,是用四个 C# 项目实现的。 |
4.Pet Shop 中的数据工厂模式
通过上面的了解,我们已经基本熟悉了Pet Shop架构,下面我们就来重点看看在这个解决方案中,是如何采用工厂模式来实现数据库的可移植性的。
在实际应用中,我们经常会遇到开发的软件产品需要同时支持不同的数据库平台,比如Oracle和SQL Server以及DB2。 而在设计应用程序的数据库访问机制时,我们可以选择应该使用哪一个数据库提供程序;可以使用通用的 OLE-DB 托管提供程序或者数据库特定的优化了性能的 .NET 托管提供程序,比如 .NET 框架中提供的 SQL Server 和 Oracle 托管提供程序。 应用程序的关键需求之一是创建一个高性能的解决方案,因此,我们选择用数据库本身的 .NET 托管提供程序构建应用程序。 关于托管提供程序和通用 OLE-DB 提供程序之间的性能差异分析,读者可以参考 Using .NET Framework Data Provider for Oracle to Improve .NET Application Performance,该文档说明了厂商特定的提供程序能够比等价的 OLE-DB 提供程序性能好两到三倍。 在选择数据库特定的访问类时进行的考虑是,我们需要为每个要支持的数据库平台写一个单独的数据访问层,因此应用程序将包含更多代码。 虽然两个数据访问层共享很多公共代码,但还是要明显地分别针对具体数据库(Oracle 或 SQL Server 2000)。
为了简化数据库访问类的使用,我们选择 GoF (译注:指 Erich Gamma 等著《设计模式》一书)概述的工厂设计模式,通过反射动态在运行时加载正确的数据访问对象。 工厂设计模式是这样实现的: 创建一个 C# 接口,其中对于数据库访问类必须公开的每个方法都要声明一个方法。 对于每一个要支持的数据库,都创建一个实现数据库特定代码的具体类,以执行接口也称“协定”中的每一项操作。 为了支持运行时确定加载哪一个具体类,需要创建第三个类,也就是工厂类,它从配置文件中读入一个值以确定应该使用反射加载哪一个程序集。 通过 .NET 的反射命名空间,可以加载某个特定程序集并用该程序集创建某个对象的实例。 为了使应用程序更安全,为版本控制提供更好的支持,我们可以在应用程序配置文件(也就是这里的 web.config. )中添加要加载的程序集文件的“证据”,这意味着 .NET 框架将只加载我们在编译期间签过名而且有正确版本号的程序集。这一创建的解决方案最重要的优势是数据库访问类可以在业务逻辑类之后编译,只要数据访问类实现了 IDAL 接口。
<!—当前调用的是SqlServer 的实现类 --> <add key="WebDAL" value="PetShop.SQLServerDAL" /> <add key="OrdersDAL" value="PetShop.SQLServerDAL" />
BLL层的Item类实现业务逻辑,代码如下:
Item item = new Item(); //调用BLL层的Item类 itemsByProduct = item.GetItemsByProduct(productId); //实现查询方法
BLL层的Item类实现业务逻辑,代码如下:
using System.Collections; //References to PetShop specific libraries //PetShop busines entity library using PetShop.Model; //PetShop DAL interfaces using PetShop.IDAL; namespace PetShop.BLL ...{ /**//// <summary> /// A business component to manage product items /// </summary> public class Item ...{ /**//// <summary> /// A method to list items by productId /// Every item is associated with a parent product /// </summary> /// <param name="productId">The productId to search by</param> /// <param name="productName">Name of the product associated with the product</param> /// <returns>An interface to an arraylist</returns> public IList GetItemsByProduct(string productId) ...{ // Validate input if (productId.Trim() == string.Empty) return null; // 通过数据访问工厂类创建访问实例 IItem dal = PetShop.DALFactory.Item.Create(); // 实例实现了IDAL接口,调用实例实现查询 return dal.GetItemsByProduct(productId); } } }
数据工厂类根据web.config中的配置创建实例,代码如下:
using System; using System.Reflection; using System.Configuration; namespace PetShop.DALFactory ...{ /**//// <summary> /// Factory implementaion for the Item DAL object /// </summary> public class Item ...{ public static PetShop.IDAL.IItem Create() ...{ /**////查找系统设置 string path = System.Configuration.ConfigurationSettings.AppSettings["WebDAL"]; string className = path + ".Item"; // 通过config文件中给定的前缀创建实例 return (PetShop.IDAL.IItem)Assembly.Load(path).CreateInstance(className); } } }
IDAL层IItem 定义了数据访问接口,代码如下:
using System.Collections; //References to PetShop specific libraries //PetShop busines entity library using PetShop.Model; namespace PetShop.IDAL...{ /**//// <summary> /// Interface to the Item DAL /// </summary> public interface IItem...{ IList GetItemsByProduct(string productId); ItemInfo GetItem(string itemId); } }
SQLServerDAL层实现了IDAL层接口,完成了具体的数据库访问,代码如下:
using System; using System.Collections; using System.Data; using System.Diagnostics; using System.Data.SqlClient; using PetShop.Model; using PetShop.IDAL; namespace PetShop.SQLServerDAL ...{ public class Item : IItem...{ // Static constants private const string SQL_SELECT_ITEM = "SELECT Item.ItemId, Item.Attr1, Inventory.Qty, Item.ListPrice, Product.Name, Product.Descn FROM Item INNER JOIN Inventory ON Item.ItemId = Inventory.ItemId INNER JOIN Product ON Item.ProductId = Product.ProductId WHERE Item.ItemId = @ItemId"; private const string SQL_SELECT_ITEMS_BY_PRODUCT = "SELECT ItemId, Attr1, ListPrice, Name FROM Item INNER JOIN Product ON Item.ProductId = Product.ProductId WHERE Item.ProductId = @ProductId"; private const string PARM_ITEM_ID = "@ItemId"; private const string PARM_PRODUCT_ID = "@ProductId"; /**//// <summary> /// Function to get a list of items within a product group /// </summary> /// <param name="productId"></param> /// <returns></returns> public IList GetItemsByProduct(string productId) ...{ IList itemsByProduct = new ArrayList(); SqlParameter parm = new SqlParameter(PARM_PRODUCT_ID, SqlDbType.Char, 10); parm.Value = productId; //Execute the query against the database using (SqlDataReader rdr = SQLHelper.ExecuteReader(SQLHelper.CONN_STRING_NON_DTC, CommandType.Text, SQL_SELECT_ITEMS_BY_PRODUCT, parm)) ...{ // Scroll through the results while (rdr.Read())...{ ItemInfo item = new ItemInfo(rdr.GetString(0).Trim(), rdr.GetString(1), rdr.GetDecimal(2), rdr.GetString(3), null); //Add each item to the arraylist itemsByProduct.Add(item); } } return itemsByProduct; } /**//// <summary> /// Get an individual item based on a the unique key /// </summary> /// <param name="itemId">unique key</param> /// <returns></returns> public ItemInfo GetItem(string itemId) ...{ //Set up a return value ItemInfo item = null; //Create a parameter SqlParameter parm = new SqlParameter(PARM_ITEM_ID, SqlDbType.Char, 10); //Bind the parameter parm.Value = itemId; //Execute the query using (SqlDataReader rdr = SQLHelper.ExecuteReader(SQLHelper.CONN_STRING_NON_DTC, CommandType.Text, SQL_SELECT_ITEM, parm)) ...{ rdr.Read(); item = new ItemInfo(rdr.GetString(0).Trim(), rdr.GetString(1), rdr.GetInt32(2), rdr.GetDecimal(3), rdr.GetString(4), rdr.GetString(5)); } return item; } } }
同样,OracleDAL层也实现了IDAL层接口,完成了Oracle的数据库访问。
图3:.NET Pet Shop 3.0 中的数据工厂的实现
这意味着,如果要创建应用程序的 DB2 版本,我们不需要改动业务逻辑层(或者 UI 层)。 创建 DB2 兼容版本的步骤如下:
1.创建 DB2 的数据库访问类,它应该实现 IDAL 接口。
2.将 DB2 访问类编译成一个程序集。
3.测试和部署新的数据程序集到一台运行中的服务器上。
4.更改配置文件,指向新的数据库访问类。
无需更改或重新编译业务逻辑组件。