前面的文章对Petshop的多层应用和表现层进行了介绍。本篇将详细介绍PetShop3.0的数据访问层实现,及其优缺点。
PetShop3.0的Oracle数据层实现在OracleDAL工程中。
一.OraHelper
OraHelper是一个辅助类,包装了连接,参数,简单的sql支持和DataReader管理。
1.连接
连接字符串在配置文件中配置,并可以对连接字符串加密。
public static readonly string CONN_STRING_NON_DTC = ConnectionInfo.DecryptDBConnectionString(ConfigurationSettings.AppSettings["OraConnString1"]);该句读取Oracle连接字符串并解密。
2.参数缓存
public static void CacheParameters(string cacheKey, params OracleParameter[] cmdParms)和public static OracleParameter[] GetCachedParameters(string cacheKey)方法用于缓存参数和从缓存中获取参数,提高性能。
3.简单sql支持
ExecuteScalar方法用于执行返回一个标量值的sql或sp。
ExecuteNonQuery方法用于执行非查询语句。
对于其他的复杂sql,客户端通过ExecuteReader获取DataReader,然后的操作由客户端负责。
二.具体DAO实现类
1.Account的public AccountInfo SignIn(string userId, string password)方法
private const string SQL_SELECT_ACCOUNT = "SELECT Account.Email, Account.FirstName, Account.LastName, Account.Addr1, Account.Addr2, Account.City, Account.State, Account.Zip, Account.Country, Account.Phone, Profile.LangPref, Profile.FavCategory, Profile.MyListOpt, Profile.BannerOpt FROM Account,Profile, SignOn WHERE Account.UserId = Profile.UserId AND Account.UserId = SignOn.UserName AND SignOn.UserName = :UserId AND SignOn.Password = :Password";
public AccountInfo SignIn(string userId, string password) {
// Get the cached parameters
OracleParameter[] signOnParms = GetSignOnParameters();
// Bind parameters
signOnParms[0].Value = userId;
signOnParms[1].Value = password;
// Using a datareader execute a query
using (OracleDataReader rdr = OraHelper.ExecuteReader(OraHelper.CONN_STRING_NON_DTC, CommandType.Text, SQL_SELECT_ACCOUNT, signOnParms)) {
if (rdr.Read()) {
//Addr2 can be null
string addr2;
if (rdr.IsDBNull(4)){
addr2 = "";
}else{
addr2 = rdr.GetString(4);
}
AddressInfo myAddress = new AddressInfo(rdr.GetString(1), rdr.GetString(2), rdr.GetString(3), addr2, rdr.GetString(5), rdr.GetString(6), rdr.GetString(7), rdr.GetString(8), rdr.GetString(9));
return new AccountInfo(userId, password, rdr.GetString(0), myAddress, rdr.GetString(10), rdr.GetString(11), Convert.ToBoolean(rdr.GetInt32(12)), Convert.ToBoolean(rdr.GetInt32(13)));
}
return null;
}
}
2.Product的public IList GetProductsBySearch(string[] keywords) 方法
//Static constants
private const string SQL_SELECT_PRODUCTS = "SELECT ProductId, Name, Descn FROM Product";
private const string SQL_SELECT_PRODUCTS_BY_CATEGORY = "SELECT ProductId, Name FROM Product WHERE Category = :Category";
private const string SQL_SELECT_PRODUCTS_BY_SEARCH1 = "SELECT ProductId, Name, Descn FROM Product WHERE ((";
private const string SQL_SELECT_PRODUCTS_BY_SEARCH2 = "LOWER(Name) LIKE '%' || {0} || '%' OR LOWER(Category) LIKE '%' || {0} || '%'";
private const string SQL_SELECT_PRODUCTS_BY_SEARCH3 = ") OR (";
private const string SQL_SELECT_PRODUCTS_BY_SEARCH4 = "))";
private const string PARM_CATEGORY = ":Category";
private const string PARM_KEYWORD = ":Keyword";
public IList GetProductsBySearch(string[] keywords) {
IList productsBySearch = new ArrayList();
//Create a new query string
int numKeywords = keywords.Length;
StringBuilder sql = new StringBuilder(SQL_SELECT_PRODUCTS_BY_SEARCH1);
//Add each keyword to the query
for (int i = 0; i < numKeywords; i++) {
sql.Append(string.Format(SQL_SELECT_PRODUCTS_BY_SEARCH2, PARM_KEYWORD + i));
sql.Append(i + 1 < numKeywords ? SQL_SELECT_PRODUCTS_BY_SEARCH3 : SQL_SELECT_PRODUCTS_BY_SEARCH4);
}
//See if we have a set of cached parameters based on a similar qquery
string sqlProductsBySearch = sql.ToString();
OracleParameter[] parms = OraHelper.GetCachedParameters(sqlProductsBySearch);
// If the parameters are null build a new set
if (parms == null) {
parms = new OracleParameter[numKeywords];
for (int i = 0; i < numKeywords; i++)
parms[i] = new OracleParameter(PARM_KEYWORD + i, OracleType.VarChar, 80);
// Cache the new parameters
OraHelper.CacheParameters(sqlProductsBySearch, parms);
}
// Bind the new parameters
for (int i = 0; i < numKeywords; i++)
parms[i].Value = keywords[i];
//Finally execute the query
using (OracleDataReader rdr = OraHelper.ExecuteReader(OraHelper.CONN_STRING_NON_DTC, CommandType.Text, sqlProductsBySearch, parms)) {
while (rdr.Read()){
ProductInfo product = new ProductInfo(rdr.GetString(0), rdr.GetString(1), rdr.GetString(2));
productsBySearch.Add(product);
}
}
return productsBySearch;
}
3.Account的public void Insert(AccountInfo acc) 方法。
public void Insert(AccountInfo acc) {
// Get the parameters for each of the 3 queries required
OracleParameter[] signOnParms = GetSignOnParameters();
OracleParameter[] accountParms = GetAccountParameters();
OracleParameter[] profileParms = GetProfileParameters();
// Bind the parameters
signOnParms[0].Value = acc.UserId;
signOnParms[1].Value = acc.Password;
SetAccountParameters(accountParms, acc);
SetProfileParameters(profileParms, acc);
// Create a connection
using (OracleConnection conn = new OracleConnection(OraHelper.CONN_STRING_NON_DTC)) {
conn.Open();
// Start an ADO.NET transactions
using (OracleTransaction trans = conn.BeginTransaction()) {
try {
//Execute the 3 queries
OraHelper.ExecuteNonQuery(trans, CommandType.Text, SQL_INSERT_SIGNON, signOnParms);
OraHelper.ExecuteNonQuery(trans, CommandType.Text, SQL_INSERT_ACCOUNT, accountParms);
OraHelper.ExecuteNonQuery(trans, CommandType.Text, SQL_INSERT_PROFILE, profileParms);
//if we reach this point then commit the database work
trans.Commit();
}catch {
//if an expection occurs, rollback all the work
trans.Rollback();
throw;
}
}
}
三.分析
从上面三个方法可以看出,对于复杂查询PetShop主要使用DataReader,而没有使用DataSet,这样的实现比较轻量,有利于系统的性能提升。我们看看在PetShop的数据处理层,我们有哪些常规工作要做。
1).写sql
PetShop的实现方式是在相应的文件中以private const string 方式定义sql的。对于复杂的sql,可以需要定义几段,在程序中根据逻辑构造合适的sql。
2).sql参数缓存
比如"SELECT ProductId, Name FROM Product WHERE Category = :Category"; PetShop中的参数都是以:Category类似的命名参数实现的。这样的参数需要我们构造,并进行缓存。
OracleParameter[] parms = OraHelper.GetCachedParameters(sqlProductsBySearch);
// If the parameters are null build a new set
if (parms == null) {
parms = new OracleParameter[numKeywords];
for (int i = 0; i < numKeywords; i++)
parms[i] = new OracleParameter(PARM_KEYWORD + i, OracleType.VarChar, 80);
// Cache the new parameters
OraHelper.CacheParameters(sqlProductsBySearch, parms);
}
3).sql参数绑定
// Bind the new parameters
for (int i = 0; i < numKeywords; i++)
parms[i].Value = keywords[i];
4).从Helper类获取DataReader,并对DataReader进行处理
using (OracleDataReader rdr = OraHelper.ExecuteReader(OraHelper.CONN_STRING_NON_DTC, CommandType.Text, sqlProductsBySearch, parms)) {
while (rdr.Read()){
ProductInfo product = new ProductInfo(rdr.GetString(0), rdr.GetString(1), rdr.GetString(2));
productsBySearch.Add(product);
}
}
我们需要对要处理的DataReader十分清楚,并作对象映射ProductInfo product = new ProductInfo(rdr.GetString(0), rdr.GetString(1), rdr.GetString(2));同时还需要进行空值处理:
string addr2;
if (rdr.IsDBNull(4)){
addr2 = "";
}else{
addr2 = rdr.GetString(4);
}
5).对于写数据库操作要处理事务
例如Account类的Insert方法的using (OracleTransaction trans = conn.BeginTransaction()) 块。
6).异常处理
PetShop中数据层的异常处理比较少,有异常处理的地方大部分都是需要事务的地方。
7).日志管理
PetShop中没有日志功能。
8).连接管理
PetShop中没有连接池,对于连接一般是在每次命令执行前都建立一个新连接,例如:
using (OracleConnection conn = new OracleConnection(connString)) 和 OracleConnection conn = new OracleConnection(connString);
要使用ADO.NET内置的连接池,可以通过在connString中配置。
9).数据缓存
PetShop中的数据层应该是没有考虑数据缓存,通过ASP.NET2.0的缓存功能可以缓解这一点,但并不能从根本上解决。
10).对多数据库和多数据源的支持
PetShop对多数据库的设计不敢叫人恭维,它自己虽然提供了对Oracle和SqlServer的支持,但这种支持是基于DAO的。而基于Oracle和SqlServer的实现是完全隔离的,程序员需要为Oracle和SqlServer分别写实现,而不能共用一些代码。这样加大了开发和维护的工作量。
所以,PetShop毕竟是一个演示系统,不具备实际应用应该具有的很多特性,它只是从架构上说明了系统应该怎样划分,而不会在细节上面面俱到。
但是我们也不能说PetShop没有对数据层进行封装。我们在选用甚至设计自己的数据访问层框架时不能像PetShop一样只是做一个简单的封装,因为这样的封装对开发效率和系统性能的意义都不是很大