PetShop3.0数据访问层实现详解

前面的文章对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一样只是做一个简单的封装,因为这样的封装对开发效率和系统性能的意义都不是很大

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>