如何简化数据库的访问与操作,兼谈泛型的应用

一、一些说明及申明

    不积跬步无以至千里,不纳滴水无以成江海。本文谈到的仅仅是实际开发过程的一个简单应用,虽然显得很不深奥,但并不影响一些编程思想在其中的具体体现。授之与鱼,不如授之与渔。我期望的是,通过某个简单应用的编码过程,让大家理解某种(个)技术的发明初衷,以及它的适用场景。

    可惜的是,这里大量的内容基于个人的经验总结和理解,毕竟书读得不够、不透,如果某些措辞不当或者指代混乱希望您见谅,好在不是正式出版物,草根说错话肯定比“专家”要少挨很多砖:)

    不过两年多的工作经历,自我感觉技术上尚是浅薄,师弟、师妹们可多了解些我的工作心得,或许能少走弯路;至于大侠、小虾之辈,哂笑之后若肯指教一二最好,或者请以宽容之心释己之怀啊!


二、面向对象的抽象过程

    在进行后台开发时,我们时常要进行数据库的访问与操作,.net 提供了 IDbConnection, IDbCommand 以及 IDbDataParameter 等接口可以满足我们的日常需要。通常,我们这样获取数据或进行操作:

   1:  //ExecuteReader
   2:  using (SqlConnection con = new SqlConnection("my connection string")) {
   3:      using (SqlCommand cmd = new SqlCommand("my command string", con)) {
   4:          con.Open();
   5:          using (SqlDataReader reader = cmd.ExecuteReader()) {
   6:              while (reader.Read()) {
   7:                  //Todo
   8:              }
   9:          }
  10:      }
  11:  }
  12:   
  13:  //ExecuteScalar
  14:  using (SqlConnection con = new SqlConnection("my connection string")) {
  15:      using (SqlCommand cmd = new SqlCommand("my command string", con)) {
  16:          con.Open();
  17:          object result = cmd.ExecuteScalar();
  18:      }
  19:  }
  20:   
  21:  //ExecuteNonQuery
  22:  using (SqlConnection con = new SqlConnection("my connection string")) {
  23:      using (SqlCommand cmd = new SqlCommand("my command string", con)) {
  24:          con.Open();
  25:          int result = cmd.ExecuteNonQuery();
  26:      }
  27:  }
 

    我在做了相当长一段时间如上编码之后,发觉可以进行如下简化,创建 SqlHelper 进行辅助。相信当前很多开发人员也是这么做的(指思路相似,细节处肯定不同):

   1:  class SqlHelper {
   2:      public static SqlDataReader ExecuteReader(string conStr, string cmdStr) {
   4:          //ExecuteReader
   5:          SqlConnection con = new SqlConnection(conStr);
   6:          SqlCommand cmd = new SqlCommand(cmdStr, con);
   7:          con.Open();
   8:          return cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);//这表示操作完成后关闭该 Reader
   9:      }
  10:   
  11:      public static object ExecuteScalar(string conStr, string cmdStr) {
  12:          //ExecuteScalar
  13:          using (SqlConnection con = new SqlConnection(conStr)) {
  14:              using (SqlCommand cmd = new SqlCommand(cmdStr, con)) {
  15:                  con.Open();
  16:                  return cmd.ExecuteScalar();
  17:              }
  18:          }
  19:      }
  20:   
  21:      public static int ExecuteNonQuery(string conStr, string cmdStr) {
  22:          //ExecuteNonQuery
  23:          using (SqlConnection con = new SqlConnection(conStr)) {
  24:              using (SqlCommand cmd = new SqlCommand(cmdStr, con)) {
  25:                  con.Open();
  26:                  return cmd.ExecuteNonQuery();
  27:              }
  28:          }
  29:      }
  30:  }

    当需要传参时,要么通过 string.format() 等方法拼接 cmdStr (很不安全),要么通过原始方法,或者通过如下变通方案:

   1:  struct Parameter{
   2:      public string Name{get;set;}
   3:      public object Value{get;set;}
   4:  }
   5:  public static int ExecuteNonQuery(string conStr, string cmdStr, params Parameter[] pars) {
   6:      //ExecuteNonQuery
   7:      using (SqlConnection con = new SqlConnection(conStr)) {
   8:          using (SqlCommand cmd = new SqlCommand(cmdStr, con)) {
   9:              foreach (Parameter p in pars) {
  10:                  cmd.Parameters.AddWithValue(p.Name, p.Value);
  11:              }
  12:              con.Open();
  13:              return cmd.ExecuteNonQuery();
  14:          }
  15:      }
  16:  }

    到这一步,看上去数据库访问已经简化得差不多:

int result = SqlHelper.ExecuteNonQuery("my connection string", "my command string", 
    new Parameter() { Name = "Number", Value = 1 }, new Parameter() { Name = "Text", Value = "afaefawf" });

    或者只是一些细节上的优化:

   1:  class SqlHelperII {
   2:   
   3:      public static SqlHelperII InstanceForStudentsSystem { get; private set; }
   4:   
   5:      static SqlHelperII() {
   6:          InstanceForStudentsSystem = new SqlHelperII(System.Configuration.ConfigurationSettings
.AppSettings["StudentsSystemConnectionString"]);
   7:      }
   8:   
   9:      public SqlHelperII(string connectionString) {
  10:          this.ConnectionString = connectionString;
  11:      }
  12:   
  13:      public string ConnectionString { get; private set; }
  14:   
  15:      public int ExecuteNonQuery(string cmdStr, params string[] pars) {
  16:          //ExecuteNonQuery
  17:          using (SqlConnection con = new SqlConnection(this.ConnectionString)) {
  18:              using (SqlCommand cmd = new SqlCommand(cmdStr, con)) {
  19:                  for (int i = 0; i < pars.Length; i += 2) {
  20:                      cmd.Parameters.AddWithValue(pars[i], pars[i + 1]);
  21:                  }
  22:                  con.Open();
  23:                  return cmd.ExecuteNonQuery();
  24:              }
  25:          }
  26:      }
  27:   
  28:  }

    以往的操作可以这样进行:

   1:  int result = SqlHelperII.InstanceForStudentsSystem.ExecuteNonQuery("my command string", 
"Number", 1.ToString(), "Text", "afaefawf");

    事实上,一般情况下我个人认为这样确实足够了,但上面的代码全假设在了 SqlServer 数据库之上,一旦遇到 MySql 之类,就需要编写某个 MySqlHelper 等。另外,我在年初某个项目与 MID 层进行交互时突然悟到,其实任何数据类操作都可以抽象成类数据库的访问方式;当时我为此做了个 MIDServicesHelper,其实现了 ExecuteReader,ExecuteScalar,ExecuteNonQuery 三个方法(我的实践证明,这三个方法大概是足够用了)。

    接下来的时间里,我创建了这样的一个接口 IDbExecutor:

   1:  namespace Queen.Data {
   2:   
   3:      /// <summary>
   4:      /// 数据库服务接口, 负责进行数据的存储、读取等基本操作. 
   5:      /// 它可以是一个传统的 Sql 数据库, 也可以是其他支持数据操作的系统.
   6:      /// </summary>
   7:      public interface IDbExecutor {
   8:   
   9:          /// <summary>
  10:          /// 链接串
  11:          /// </summary>
  12:          string ConnectionString {
  13:              get;
  14:              set;
  15:          }
  16:   
  17:          /// <summary>
  18:          /// 超时设置
  19:          /// </summary>
  20:          int CommandTimeout {
  21:              get;
  22:              set;
  23:          }
  24:   
  25:          /// <summary>
  26:          /// 获取执行影响行数.
  27:          /// 实现该接口时注意释放所有相关资源! 
  28:          /// </summary>
  29:          /// <param name="cmdText"></param>
  30:          /// <param name="keyValuePairs"></param>
  31:          /// <returns></returns>
  32:          int ExecuteNonQuery(string cmdText, params string[] keyValuePairs);
  33:   
  34:          /// <summary>
  35:          /// 获取执行值.
  36:          /// 实现该接口时注意释放所有相关资源!
  37:          /// </summary>
  38:          /// <param name="cmdText"></param>
  39:          /// <param name="keyValuePairs"></param>
  40:          /// <returns></returns>
  41:          object ExecuteScalar(string cmdText, params string[] keyValuePairs);
  42:   
  43:          /// <summary>
  44:          /// 获取数据读取流.
  45:          /// 调用该接口时务必释放读取流, using(IDataReader reader = XX.ExecuteReader()){ ... }
  46:          /// 实现该接口时请勿释放任何资源, 返回调用 System.Data.SqlCommand.ExecuteReader(
System.Data.CommandBehavior.CloseConnection)
  47:          /// </summary>
  48:          /// <param name="cmdText">数据库执行文本.</param>
  49:          /// <param name="keyValuePairs">参数组, 比如 "@ParaA", "valueA", "@ParaB", "valueB"</param>
  50:          /// <returns>数据读出流</returns>
  51:          System.Data.IDataReader ExecuteReader(string cmdText, params string[] keyValuePairs);
  52:   
  53:      }
  54:  }

    凡后来我主导的项目里总会多出这样一个文件及类型 DbExecutor,其内部可定义支持 SqlServer、MySql  甚至其它数据库的内部类,但数据库类型对外界“基本”透明(组织 SQL 语句时必须知道数据库类型,SqlServer、MySql 有各自的 SQL 语言特性!):

   1:  static class DbExecutor {
   2:   
   3:      public static Queen.Data.IDbExecutor Students { get; private set; }
   4:      public static Queen.Data.IDbExecutor Families { get; private set; }
   5:   
   6:      static DbExecutor() {
   7:          //Init students, families ...
   8:      }
   9:   
  10:      private class SqlServerExecutor : Queen.Data.IDbExecutor {
  11:   
  12:          #region IDbExecutor 成员
  13:   
  14:          public string ConnectionString {
  15:              get {
  16:                  throw new NotImplementedException();
  17:              }
  18:              set {
  19:                  throw new NotImplementedException();
  20:              }
  21:          }
  22:   
  23:          public int CommandTimeout {
  24:              get {
  25:                  throw new NotImplementedException();
  26:              }
  27:              set {
  28:                  throw new NotImplementedException();
  29:              }
  30:          }
  31:   
  32:          public int ExecuteNonQuery(string cmdText, params string[] keyValuePairs) {
  33:              throw new NotImplementedException();
  34:          }
  35:   
  36:          public object ExecuteScalar(string cmdText, params string[] keyValuePairs) {
  37:              throw new NotImplementedException();
  38:          }
  39:   
  40:          public System.Data.IDataReader ExecuteReader(string cmdText, params string[] keyValuePairs) {
  41:              throw new NotImplementedException();
  42:          }
  43:   
  44:          #endregion
  45:      }
  9:  
  10:      private class MySqlExecutor : Queen.Data.IDbExecutor {
  11:   
  12:          #region IDbExecutor 成员
  13:   
  14:          public string ConnectionString {
  15:              get {
  16:                  throw new NotImplementedException();
  17:              }
  18:              set {
  19:                  throw new NotImplementedException();
  20:              }
  21:          }
  22:   
  23:          public int CommandTimeout {
  24:              get {
  25:                  throw new NotImplementedException();
  26:              }
  27:              set {
  28:                  throw new NotImplementedException();
  29:              }
  30:          }
  31:   
  32:          public int ExecuteNonQuery(string cmdText, params string[] keyValuePairs) {
  33:              throw new NotImplementedException();
  34:          }
  35:   
  36:          public object ExecuteScalar(string cmdText, params string[] keyValuePairs) {
  37:              throw new NotImplementedException();
  38:          }
  39:   
  40:          public System.Data.IDataReader ExecuteReader(string cmdText, params string[] keyValuePairs) {
  41:              throw new NotImplementedException();
  42:          }
  43:   
  44:          #endregion
  45:      }
  46:  }
 

    这样做的好处在于,不但在代码量上减少了开发人员的维护成本,也在系统的迁移上极大减少了代码替换工作(虽然迁移的可能性比较小),同时,也给系统的数据库操作监控带来了便利(比如增加日志功能等,后文将举例实现)。


三、泛型编程

    但针对数据库操作这块,我还是严重感觉不够优雅,我觉得 SqlServerExecutor、MyServerExecutor 等虽然引用了不同的驱动程序,但这些驱动程序均实现了同一类接口(IDbConnection等),通过继承实现多个版本的 XxxExecutor 简直太丑陋了!而后,通过学习、领会泛型编程思想,我当前使用的版本产生了:

   1:  using System.Data;
   2:   
   3:  namespace Queen.Data {
   4:      /// <summary>
   5:      /// 提供 Sql 语言支持的数据库服务, 必须声明为 IDbExecutor 才能使用相关服务. 
   6:      /// </summary>
   7:      /// <typeparam name="TConnection">链接类型</typeparam>
   8:      /// <typeparam name="TCommand">命令类型</typeparam>
   9:      /// <typeparam name="TDataParameter">数据参数类型</typeparam>
  10:      public class SqlExecutor<TConnection, TCommand, TDataParameter> : IDbExecutor
  11:          where TConnection : IDbConnection, new()
  12:          where TCommand : IDbCommand, new()
  13:          where TDataParameter : IDbDataParameter, new() {
  14:          
  15:          /// <summary>
  16:          /// Sql 命令类型.
  17:          /// </summary>
  18:          public System.Data.CommandType CommandType {
  19:              get;
  20:              set;
  21:          }
  22:   
  23:          #region Construct
  24:   
  25:          public SqlExecutor() {
  26:              CommandType = System.Data.CommandType.Text;
  27:              ((IDbExecutor)this).CommandTimeout = 30;
  28:          }
  29:   
  30:          public SqlExecutor(System.Data.CommandType commandType) {
  31:              CommandType = commandType;
  32:              ((IDbExecutor)this).CommandTimeout = 30;
  33:          }
  34:   
  35:          public SqlExecutor(System.Data.CommandType commandType, int commandTimeout) {
  36:              CommandType = commandType;
  37:              ((IDbExecutor)this).CommandTimeout = commandTimeout;
  38:          }
  39:   
  40:          #endregion
  41:   
  42:          #region ISqlExecutor 成员
  43:   
  44:          string IDbExecutor.ConnectionString {
  45:              get;
  46:              set;
  47:          }
  48:   
  49:          /// <summary>
  50:          /// 链接串, 默认 30(秒)
  51:          /// </summary>
  52:          int IDbExecutor.CommandTimeout {
  53:              get;
  54:              set;
  55:          }
  56:   
  57:          int IDbExecutor.ExecuteNonQuery(string cmdText, params string[] keyValuePairs) {
  58:              using (TConnection con = new TConnection()) {
  59:                  con.ConnectionString = ((IDbExecutor)this).ConnectionString;
  60:                  using (TCommand cmd = new TCommand()) {
  61:                      cmd.CommandText = cmdText;
  62:                      cmd.Connection = con;
  63:                      if (keyValuePairs != null) {
  64:                          for (int i = 0; i < keyValuePairs.Length - 1; i += 2) {
  65:                              TDataParameter p = new TDataParameter();
  66:                              p.ParameterName = keyValuePairs[i];
  67:                              p.Value = keyValuePairs[i + 1];
  68:                              cmd.Parameters.Add(p);
  69:                          }
  70:                      }
  71:                      cmd.CommandType = CommandType;
  72:                      cmd.CommandTimeout = ((IDbExecutor)this).CommandTimeout;
  73:                      con.Open();
  74:                      return cmd.ExecuteNonQuery();
  75:                  }
  76:              }
  77:          }
  78:   
  79:          object IDbExecutor.ExecuteScalar(string cmdText, params string[] keyValuePairs) {
  80:              using (TConnection con = new TConnection()) {
  81:                  con.ConnectionString = ((IDbExecutor)this).ConnectionString;
  82:                  using (TCommand cmd = new TCommand()) {
  83:                      cmd.CommandText = cmdText;
  84:                      cmd.Connection = con;
  85:                      if (keyValuePairs != null) {
  86:                          for (int i = 0; i < keyValuePairs.Length - 1; i += 2) {
  87:                              TDataParameter p = new TDataParameter();
  88:                              p.ParameterName = keyValuePairs[i];
  89:                              p.Value = keyValuePairs[i + 1];
  90:                              cmd.Parameters.Add(p);
  91:                          }
  92:                      }
  93:                      cmd.CommandType = CommandType;
  94:                      cmd.CommandTimeout = ((IDbExecutor)this).CommandTimeout;
  95:                      con.Open();
  96:                      return cmd.ExecuteScalar();
  97:                  }
  98:              }
  99:          }
 100:   
 101:          /// <summary>
 102:          /// 获取数据读取流
 103:          /// 调用该接口时务必释放读取流, using(IDataReader reader = XX.ExecuteReader()){ ... }.
 104:          /// </summary>
 105:          /// <param name="cmdText"></param>
 106:          /// <param name="keyValuePairs"></param>
 107:          /// <returns></returns>
 108:          System.Data.IDataReader IDbExecutor.ExecuteReader(string cmdText, params string[] keyValuePairs) {
 109:              TConnection con = new TConnection();
 110:              con.ConnectionString = ((IDbExecutor)this).ConnectionString;
 111:              TCommand cmd = new TCommand();
 112:              cmd.CommandText = cmdText;
 113:              cmd.Connection = con;
 114:              if (keyValuePairs != null) {
 115:                  for (int i = 0; i < keyValuePairs.Length - 1; i += 2) {
 116:                      TDataParameter p = new TDataParameter();
 117:                      p.ParameterName = keyValuePairs[i];
 118:                      p.Value = keyValuePairs[i + 1];
 119:                      cmd.Parameters.Add(p);
 120:                  }
 121:              }
 122:              cmd.CommandType = CommandType;
 123:              cmd.CommandTimeout = ((IDbExecutor)this).CommandTimeout;
 124:              con.Open();
 125:              return cmd.ExecuteReader(CommandBehavior.CloseConnection);
 126:          }
 127:   
 128:          #endregion
 129:   
 130:      }
 131:  }

只需指定 TConnection, TCommand, TDataParameter 的具体类型,该辅助类并能支持对应工作了;也就是把 SqlServer、MySql 等的具体辅助类合并到了一起并做了实现,真的是一劳永逸啊!相应的,DbExecutor 简化成了:

   1:  /// <summary>
   2:  /// 数据访问代理
   3:  /// </summary>
   4:  public static class DbExecutor {
   5:      private static IDbExecutor _files;
   6:      public static IDbExecutor Files { get { return _files; } private set { _files = value; } }
   7:      private static IDbExecutor _BK;
   8:      public static IDbExecutor BK { get { return _BK; } private set { _BK = value; } }
   9:      private static IDbExecutor _ResourcesProcedure;
  10:      public static IDbExecutor ResourcesProcedure { get { return _ResourcesProcedure; } private set {
 _ResourcesProcedure = value; } }
  11:      private static IDbExecutor _Resources;
  12:      public static IDbExecutor Resources { get { return _Resources; } private set { _Resources = value; } }
  13:      static DbExecutor() {
  14:          Files = new SqlExecutor<MySqlConnection, MySqlCommand, MySqlParameter>();
  15:          Files.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Files"]
.ConnectionString;
  16:   
  17:          BK = new SqlExecutor<MySqlConnection, MySqlCommand, MySqlParameter>();
  18:          BK.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["bk"]
.ConnectionString;
  19:   
  20:          Resources = new SqlExecutor<MySqlConnection, MySqlCommand, MySqlParameter>();
  21:          Resources.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings[
"Resources"].ConnectionString;
  22:   
  23:   
  24:          ResourcesProcedure = new SqlExecutor<MySqlConnection, MySqlCommand, MySqlParameter>(
System.Data.CommandType.StoredProcedure);
  25:          ResourcesProcedure.ConnectionString = System.Configuration.ConfigurationManager
.ConnectionStrings["Resources"].ConnectionString;
  26:      }
  27:   
  28:  }

四、装饰者设计模式的应用

    最近,组内希望添加数据库操作人员记录,我只是做了如下的改动便完成了简易日志功能(请参见“装饰者设计模式”):

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:  using Queen.Data;
   5:  using MySql.Data.MySqlClient;
   6:  using System.Web;
   7:   
   8:  /// <summary>
   9:  /// 数据访问代理
  10:  /// </summary>
  11:  public static class DbExecutor {
  12:      private static IDbExecutor _files;
  13:      public static IDbExecutor Files { get { return _files; } private set { _files = value; } }
  14:      private static IDbExecutor _BK;
  15:      public static IDbExecutor BK { get { return _BK; } private set { _BK = value; } }
  16:      private static IDbExecutor _ResourcesProcedure;
  17:      public static IDbExecutor ResourcesProcedure { get { return _ResourcesProcedure; } 
private set { _ResourcesProcedure = value; } }
  18:      private static IDbExecutor _Resources;
  19:      public static IDbExecutor Resources { get { return _Resources; } private set { _Resources = value; } }
  20:      static DbExecutor() {
  21:          Files = new SqlExecutor<MySqlConnection, MySqlCommand, MySqlParameter>();
  22:          Files.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["Files"]
.ConnectionString;
  23:          Files = new DecorateExecutor(Files);
  24:   
  25:          BK = new SqlExecutor<MySqlConnection, MySqlCommand, MySqlParameter>();
  26:          BK.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["bk"]
.ConnectionString;
  27:          BK = new DecorateExecutor(BK);
  28:   
  29:          Resources = new SqlExecutor<MySqlConnection, MySqlCommand, MySqlParameter>();
  30:          Resources.ConnectionString = System.Configuration.ConfigurationManager
.ConnectionStrings["Resources"].ConnectionString;
  31:          Resources = new DecorateExecutor(Resources);
  32:   
  33:   
  34:          ResourcesProcedure = new SqlExecutor<MySqlConnection, MySqlCommand, MySqlParameter>(
System.Data.CommandType.StoredProcedure);
  35:          ResourcesProcedure.ConnectionString = System.Configuration.ConfigurationManager
.ConnectionStrings["Resources"].ConnectionString;
  36:          ResourcesProcedure = new DecorateExecutor(ResourcesProcedure);
  37:      }
  38:   
  39:      #region DecorateExecutor, logable
  40:   
  41:      class DecorateExecutor : IDbExecutor {
  42:   
  43:          private IDbExecutor value;
  44:   
  45:          public DecorateExecutor(IDbExecutor value) {
  46:              this.value = value;
  47:          }
  48:   
  49:          #region IDbExecutor 成员
  50:   
  51:          public int CommandTimeout {
  52:              get {
  53:                  return this.value.CommandTimeout;
  54:              }
  55:              set {
  56:                  this.value.CommandTimeout = value;
  57:              }
  58:          }
  59:   
  60:          public string ConnectionString {
  61:              get {
  62:                  return this.value.ConnectionString;
  63:              }
  64:              set {
  65:                  this.value.ConnectionString = value;
  66:              }
  67:          }
  68:   
  69:          public int ExecuteNonQuery(string cmdText, params string[] keyValuePairs) {
  70:              if (null != HttpContext.Current && null != HttpContext.Current.User && null 
!= HttpContext.Current.User.Identity && !string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name)) {
  71:                  System.Diagnostics.Trace.WriteLine(string.Format("{0}\t{1} 进行了操作 {2}...",
  72:                      DateTime.Now.ToShortTimeString(), HttpContext.Current.User.Identity.Name, 
cmdText.Length > 20 ? cmdText.Substring(0, 20) : cmdText));
  73:              }
  74:              return this.value.ExecuteNonQuery(cmdText, keyValuePairs);
  75:          }
  76:   
  77:          public System.Data.IDataReader ExecuteReader(string cmdText, params string[] keyValuePairs) {
  78:              if (null != HttpContext.Current && null != HttpContext.Current.User && null 
!= HttpContext.Current.User.Identity && !string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name)) {
  79:                  System.Diagnostics.Trace.WriteLine(string.Format("{0}\t{1} 进行了操作 {2}...",
  80:                      DateTime.Now.ToShortTimeString(), HttpContext.Current.User.Identity.Name, 
cmdText.Length > 20 ? cmdText.Substring(0, 20) : cmdText));
  81:              }
  82:              return this.value.ExecuteReader(cmdText, keyValuePairs);
  83:          }
  84:   
  85:          public object ExecuteScalar(string cmdText, params string[] keyValuePairs) {
  86:              if (null != HttpContext.Current && null != HttpContext.Current.User && null 
!= HttpContext.Current.User.Identity && !string.IsNullOrEmpty(HttpContext.Current.User.Identity.Name)) {
  87:                  System.Diagnostics.Trace.WriteLine(string.Format("{0}\t{1} 进行了操作 {2}...",
  88:                      DateTime.Now.ToShortTimeString(), HttpContext.Current.User.Identity.Name,
 cmdText.Length > 20 ? cmdText.Substring(0, 20) : cmdText));
  89:              }
  90:              return this.value.ExecuteScalar(cmdText, keyValuePairs);
  91:          }
  92:   
  93:          #endregion
  94:      }
  95:   
  96:      #endregion
  97:   
  98:  }

五、小结:

    优雅的代码让人看着赏心悦目,虽然每个人的审美会有不同,但至少要让自己觉得舒服。我尝试着在编码的过程中积累软件设计的经验,充分发挥面向对象的长处,使得代码的可复用性、可维护性(可调试的能力?)、可扩展性达到极致。在本文前半段,我与大家分享了我在抽象数据库访问行为时的心得(不过是 IDbExecutor 接口),而在接下来的文字里,我使用了泛型编程技巧,使得传统的面向对象(通过继承等手段扩展、复用代码?)得到了看似质的突破,极大缓解了“继承泛滥”的苦恼。

    面向过程编程,大量的操作是参数的传递,由于参数之间有逻辑上的关联,且“过程”本身又针对某类关联参数,于是有了“面向对象”。这样一来,人们掌握系统全局的能力得到了加强,从 .net 框架提供的各种服务来看,面向对象实质上降低了编程的门槛,并且提高了人们的生产效率。

    通常,除非使用反射,我们很难使得代码不经过扩展而达到“向后兼容”。但反射本身并不优雅,而且执行效率会降低,泛型的推出解决了这样一个矛盾。我个人的理解是,可以将所谓“泛型”看成“类型参数”,传统上,参数基本是基于数据的,而引入了类型参数后,不但不会失去强类型的类型安全特点,还能使得代码被编译器优化,提高执行效率(是指“值类型的装箱、拆箱”操作等)。

    但从来没有完美的事,在当真要进行数据库转换、系统迁移的时候,比如从 SqlServer 变为 MySql 时,当前的 IDbExecutor 是不可能让数据库类型透明的:因为开发人员在组织 SQL 语句时必须知道数据库类型,SqlServer、MySql 有各自的 SQL 语言特性,相同的 SQL 语句并不能保证在两个数据库平台上成功运行!解决这个缺陷的办法,除了阉割当前数据库的 SQL 语言特性外(指禁止使用),我看是无解的;但分页操作(关键字 TOP/LIMIT)是无法回避的,而且,在 SqlServer 中参数使用“@p”,而 MySql 中使用“?p”,诸如此类,因此,这个缺陷就是无解的!数据库类型变化时,SQL 语句的改造再所难免,只不过从代码变化的量上,当前的数据访问方式要小得多!

(完)

posted @ 2009-07-14 16:47  陛下  阅读(3930)  评论(23编辑  收藏  举报