概述ADONET为我们提供了强大的数据库开发能力它内置的多个对象为我们的数据库编程提供了不同的选择但是在允许我们灵活选用的同时许多初学者也很迷惑我到底是应该使用DataReader还是应该使用DataAdapter?我只想读取一小部分数据难道我一定要Fill满整个DataSet吗?为什么DataReader不能和RecordSet一样提供一个数据更新的方法?DataSet到底有什么好处?
在本文中我将 PetShop的数据库编程模式和Duwamish的数据库编程模式进行一些简单的分析和对比如果您也有以上疑问的话相信在读完本文之后就可以根据具体的需要来制定一个最适合您应用的数据库编程模式
Net PetShop和Duwamish简单介绍
相信大家一定听说过有名的宠物店大战没错本文的主角之一就是获胜方Net PetShop微软号称以倍的速度和/的代码量遥遥领先于基于JEE的PetStore宠物商店虽然SUN也曾对此抱怨过不满指责此大战有水分不过无论如何Net PetShop绝对是一个经典的Net实例教程至少为我们提供了一条赶超JEE的捷径= )它的下载地址是
Net PetShop宠物网上商店首页
而Duwamish则是一个外表简单内部却极其复杂的一个网上书店的Net完整应用范例作为一个微软官方的Sample它同时提供了C#和VBNet两种语言版本并且还附上了大量详尽的中文资料如果打印出来实在是居家旅行临睡入厕必备之物什么?您没听说过?呵呵如果您装了Visual Studio Net的话它就在您的硬盘上静静的躺着呢不过还没有被安装您可以在您的 的EntERPrise Samples目录下找到并安装它例如C:\Program Files\Microsoft Visual Studio NET\Enterprise Samples\Duwamish CS
Duwamish网上电子书店首页
结构简述
两家商店都采用了n层应用结构(毫无疑问n层结构的应用架构应该绝对是您开发Net应用的首选哪怕您只想做一个网页计数器)不同的是PetShop采用的是最常见的三层应用结构分别为表示层中间层和数据层而Duwamish则采用的是一个四层应用结构并使用不同的项目分隔开分别为表示层业务外观层业务规则层和数据层至于这两种结构分别有什么优点和缺点以及为什么要这么分层我们不进行详细讨论因为本文的重点不在于此我们主要分析的是他们的数据库编程的模式
Duwamish数据访问剖析
首先我们来看看Duwamish书店它采用的是DataAdapter和DataSet配合的数据存储模式所不同的是它对DataSet进行子类化扩展作为数据载体也就是采用定制的DataSet来进行层间的数据传输下面是一个定制的DataSet示例
public class BookData : DataSet
{
public BookData()
{
//
// Create the tables in the dataset
//
BuildDataTables();
}
private void BuildDataTables()
{
//
// Create the Books table
//
DataTable table = new DataTable(BOOKS_TABLE);
DataColumnCollection columns = tableColumns;
columnsAdd(PKID_FIELD typeof(SystemInt));
columnsAdd(TYPE_ID_FIELD typeof(SystemInt));
columnsAdd(PUBLISHER_ID_FIELD typeof(SystemInt));
columnsAdd(PUBLICATION_YEAR_FIELD typeof(SystemInt));
columnsAdd(ISBN_FIELD typeof(SystemString));
columnsAdd(IMAGE_FILE_SPEC_FIELD typeof(SystemString));
columnsAdd(TITLE_FIELD typeof(SystemString));
columnsAdd(DESCRIPTION_FIELD typeof(SystemString));
columnsAdd(UNIT_PRICE_FIELD typeof(SystemDecimal));
columnsAdd(UNIT_COST_FIELD typeof(SystemDecimal));
columnsAdd(ITEM_TYPE_FIELD typeof(SystemString));
columnsAdd(PUBLISHER_NAME_FIELD typeof(SystemString));
thisTablesAdd(table);
}
………
}
我们可以看到它有一个BuildDataTables方法并且在构造函数中调用这样定制的Books表就和这个DataSet捆绑在一起了省得以后还要进行Column Mapping这真是个好主意我怎么就没有想到呢? )
解决了数据结构接下来看看数据层的代码实现在Duwamish中数据层中有个类分别是BooksCategoriesCustomers和Orders每个类分别只负责有关数据的存取下面是其中一个类的示例代码
private SqlDataAdapter dsCommand;
public BookData GetBookById(int bookId)
{
return FillBookData(GetBookById @BookId bookIdToString());
}
private BookData FillBookData(String commandText String paramName String paramValue)
{
if (dsCommand == null )
{
throw new SystemObjectDisposedException( GetType()FullName );
}
BookData data = new BookData();
SqlCommand command = dsCommandSelectCommand;
commandCommandText = commandText;
commandCommandType = CommandTypeStoredProcedure; // use stored proc for perf
SqlParameter param = new SqlParameter(paramName SqlDbTypeNVarChar );
paramValue = paramValue;
commandParametersAdd(param);
dsCommandFill(data);
return data;
}
这里就是数据层的代码了我们在这里可以看到Duwamish采用了DataAdapter来将数据填充到定制的DataSet中然后返回该DataSet我感到很奇怪的是在数据存取层中竟然可以看到GetBookById这样具体的数据存取方法虽然最后还是有一个抽象出来的FillBookData方法但是上面还有三层啊底层都做到这份上了那上层都做些什么呢?答案是数据检查上层基本上都在做一些很严密的数据合法性校验(当然也会包括一些比较复杂的事务逻辑但是并不多)示例代码如下
public CustomerData GetCustomerByEmail(String emailAddress String password)
{
//
// Check preconditions
//
ApplicationAssertCheckCondition(emailAddress != StringEmpty Email address is required
ApplicationAssertLineNumber);
ApplicationAssertCheckCondition(password != StringEmpty Password is required
ApplicationAssertLineNumber);
//
// Get the customer dataSet
//
CustomerData dataSet;
using (DataAccessCustomers customersDataAccess = new DataAccessCustomers())
{
dataSet = customersDataAccessLoadCustomerByEmail(emailAddress);
}
//
// Verify the customers password
//
DataRowCollection rows = dataSetTables[CustomerDataCUSTOMERS_TABLE]Rows;
if ( ( rowsCount == ) && rows[][CustomerDataPASSWORD_FIELD]Equals(password) )
{
return dataSet;
}
else
{
return null;
}
}
在这个方法中真正进行数据存取的实际上只有
dataSet = customersDataAccessLoadCustomerByEmail(emailAddress);
这么一句是直接调用的数据层其它都是在进行合法性校验我们可以感悟到进行一个真正的企业级开发需要考虑的系统健壮性有多么重要
PetShop数据访问剖析
OKDuwamish看完了下面我们来看看PetShop的数据访问机制
PetShop只有一个项目它采用的分层办法是将中间层和数据层都写成cs文件放在Components目录里其中数据层就是一个名为Database的类它封装了所有对数据库的底层操作下面是示例代码段
public void RunProc(string procName out SqlDataReader dataReader) {
SqlCommand cmd = CreateCommand(procName null);
dataReader = cmdExecuteReader(SystemDataCommandBehaviorCloseConnection);
}
我们看到了一个跟Duwamish截然不同的另一种数据访问方式它将所有的数据访问方法抽象出来做成一个RunProc方法至于返回数据呢呵呵它有点偷懒直接返回一个DataReader给你你自己去读吧还记得Duwamish采用的层间数据传输载体是什么吗?对了是DataSet它被数据层填充后返回给了中间层但是这里数据层和传输层的数据传输载体变成了DataReader实际上还不能称它为数据载体因为数据还没开始读呢在这里DataReader的作用和指针有点类似也许我们应该称它为数据引用)
接着往下看DataReader被怎么处理的
public ProductResults[] GetList(string catid int currentPage int pageSize ref int numResults)
{
numResults = ;
int index=;
SqlDataReader reader = GetList(catid);
ProductResults[] results = new ProductResults[pageSize];
// now loop through the list and pull out items of the specified page
int start = (int)((currentPage ) * pageSize);
if (start <= ) start = ;
// skip
for (int i = ; i < start ; i++) {
if (readerRead()) numResults++;
}
if (start > ) readerRead();
// read the data we are interested in
while (readerRead()) {
if (index < pageSize) {
results[index] = new ProductResults();
results[index]productid = readerGetString();
results[index]name = readerGetString();
index++;
}
numResults++;
}
readerClose();
// see if need to redim array
if (index == pageSize)
return results;
else {
// not a full page redim array
ProductResults[] results = new ProductResults[index];
ArrayCopy(results results index);
return results;
}
}
注意到currentPage和pageSize了吗?原来在这里就进行了数据分页只返回满足需要的最少的数据量而不是象我们很多喜欢偷懒的人一样简单的将整个DataTable一股脑的绑定到DataGrid造成大量的数据冗余
在这里数据被真正的读出来并且被手动填充到一个自定义的对象数组中我们来看看这个数组的定义
public class ProductResults
{
private string m_productid;
private string m_name;
// product props
public string productid {
get { return m_productid; }
set { m_productid = value; }
}
public string name {
get { return m_name; }
set { m_name = value; }
}
}
非常之简单不过我有点奇怪为什么不使用struct呢?是不是Net中struct和class的性能差距已经可以忽略不计了?
分析总结
通过观察这两个商店的具体实现我们得到了两个不同的数据访问模式Duwamish采用的是以DataSet为核心因为DataSet提供了这方面大量的相关方法所以整个应用的数据传输数据格式定义数据校验都围绕着DataSet来进行整个架构定义非常清晰和严谨但是却显得有些庞大PetShop在整个程序中没有采用一个DataSet程序非常的简洁轻灵但是没有Duwamish那么强的健壮性这两个程序是Microsoft公司不同的小组写出来的代码所以有着不同风格不过都应该能代表Net的标准模式看到这里你应该对文章开头提出的那些疑问有一个比较形象的认识了吧
另外请再次注意PetShop在打开数据连接之后并没有马上读取数据而是将DataReader传递给另外的对象来执行数据读的操作然后才关闭连接这样数据连接的时间加长了而数据库连接是一项非常宝贵的服务器资源相比之下Dawamish在连接数据库之后马上进行填充然后迅速释放掉数据库连接的方式更加有利于大量用户的并发访问
再一点上文的程序中没有提到更新操作PetShop采用的是使用Command对象执行单个存储过程的方式来进行更新操作是属于一种在线即时数据更新模式而Dawamish采用的是DataAdapter的Update方法将DataSet的改变一次性的提交到数据库中属于离线数据更新模式这种模式的好处是可以一次性更新大批量数据减少数据库的连接次数缺点是如果数据库在改动非常频繁的情况下需要实时的跟蹤数据变化就不合适了需要根据具体的情况采用具体的数据更新办法
总的来说如果您只需要快速的读取数据并显示出来推荐您采用DataReader如果您需要对数据进行大量的修改还有大量并发访问的可能而且不需要实时的跟蹤数据库的变化推荐您使用DataSet当然这两种情况有点极端了实际的应用环境也许有着很复杂的条件具体需要您自己审时度势综合采用不过我个人还是比较喜欢PetShop那种轻灵的风格 )
本文只尝试对以上两个典型的Net应用例程的数据访问机制做了一个简单的追蹤分析