• 数据访问层DAL的再次重构_3(转载)


    原文链接

    接着数据访问层DAL的再次重构_2_模块的自定义设置节我们继续实现,到这里,开始后台编码,经历了建立数据库、建表、存储过程、web.config的自定义配置节点后,我们来实现数据访问层的编码。

    首先:用OOP方式来映射后台的表Polls和PollOptions。分别命名为PollDetail和PollOptionDetail简单实体类。

    image

    注意一些转换映射的事项:

    • 简单实体就是对表的列进行封装。映射为对应的属性时注意赋予初始值。

             (如日期字段的复制根据需要选择 DateTime.Now 和 DateTime.MinValue、DateTime.MaxValue)

    • 构造方法需要重载(添加带参数的构造方法,实现对需要的属性赋值)
    • 私有字段的命名一般以 _ 下划线开头,采用骆驼命名法
    • 不管实际的数据库的表的主键字段是什么,这里都用Id属性名统一
    • 可能会增加自己的属性[如Votes](表Polls并没有该列,但是获取记录的存储过程确提供了<详情见DAL的再次重构_1>)

    image

    这里的命名空间定义为DAL(通常是DAL.Entity<根文件夹名层次匹配>)。实际上简单实体类的编码通常用代码生成器自动完成。(如CodeSmith工具)

    PollOptionDetail.cs代码如下:(增加了Percentage属性:调查选项占总投票数的百分比)

    image

    上面的构造方法都重载了(增加了带参数的功能,便于初始化。)

    我们还是考虑使用多种类型数据库的可能,比如使用Oracle数据库/SqlServer数据库/DB2数据库等等。这是通过不同的数据提供程序[Provider]来访问的。

    image

    比如获取的数据来源类型不同,匹配的数据源控件就不同,如(SqlDataSource1/AccessDataSource1/XmlDataSource1等等。。。)

    尽管名称不同,但都是DataSource数据源控件,根据你选择的不同,就自动匹配不同的具体数据源控件。

    我们这里不管是哪种类型的数据库,对表Polls和表PollOptions的访问都是增删改查(CRUD),直白说就是那些存储过程所实现的功能。所以这里建立抽象类PollsProvider来实现这些功能封装。具体的实现代码则交给其后的子类SqlPollsProvider来完成。(AccessPollsProvider的代码省略)

    建立抽象的父类:PollsProvider用来把模块相关的实体的操作方法集中放在一起,这样比较合理紧凑。

    BLL(业务逻辑层与本类进行交互,类似于简单工厂由该类提供具体的子类来完成实际的底层操作)

    注意事项:

    • 基本上是对存储过程的封装,方法名与存储过程名可以相同,存储过程的参数为方法的参数
    • 命名规范便于以后代码生成器的实现

    image

    实际上而言:应该还有个更高的父类:DataAccess,然后:PollsProvider:DataAccess,因为网站不止一个模块,会出现更多的XXXProvider,它们的通用操作就可以放在父类DataAccess中,以继承的方式拥有。而且每个模块有各自的缓存时间、是否激活缓存等等设置。

    所以这里添加DataAccess类。

    image

    这样修改PollsProvider构造方法,实现从DataAccess继承的属性的初始化赋值。(这些都是通过读取web.config自定义配置的部分<详见自定义配置节>)

     image

    接下来实现具体的子类SqlPollsProvider实现对SqlServer数据库的访问。

    image

    image

    这里我们就发现这两个方法出现重复代码,需要抽取出来重构成新方法。我认为原则就是找到变和不变的地方。(变的地方用参数灵活处理,不便的地方就用代码方式固定下来)。仔细分析一下就得知:实质上就是调用SqlCommand对象执行存储过程。(而存储过程的名成和参数是变化的),那么提取的重构方法如下:

    image

    接着编写下面的方法,如下图:

    image

    虽然此时,并没有出现重复的代码情况,但是根据前面的经验,我们这里也有能力进行代码重构了,改动如下:

    image

    该图中的第二个红色框框实现的是从SqlDataReader中读取当前记录各列的数据,然后初始化为实体对象PollDetail(调用了带参数的构造方法)

    虽然没有编写后面的GetPolls方法,但是可以肯定这段代码肯定会重复的。所以提取出来,改动后如下:

    image

    我们可以考虑到如果实现AccessPollsProvider的这一部分时,会出现如下的情况:

    image

    其实不难发现:AccessPollsProvider和SqlPollsProvider的私有方法GetPollFromReader重复,仅仅参数不同:但是它们都实现了接口IDataReader。所以提取到父类PollsProvider中。进一步,我们接着也封装了后面的三个受保护的GetXXXX方法。(见下图)

    image

    这样原有的子类中的私有方法GetPollFromReader就可以去掉了。

    image

    我们发现上图的这两个红色框框部分能否重构呢?

    78

    其实也是可以的,我们知道通过Connection对象的CreateCommand 方法能得到对应的Command对象,而SqlParameter与OleDbParameter的父类都是同一个DbParameter。而Connection的父类都是DbConnection

    image

    那么我们实现提取到父类(DataAccess)中(这里应该叫祖先类了) ,仔细想想就会明白放在DataAccess类中更通用。为了区别,我们把这个方法定义为Execute。

    image

    对应的子类调用的代码如下:

    image

    感觉比起原来的ExecuteCmd方法反而多了一个Connection对象参数,能不能再方便些了?

    10 image

    其实还是可以实现的。

    (来源请参考……

    image

    image

    image

    image

    image

    ……)

    回到我们的这个主题上来,运用模板方法模式,我们继续改善:

    image

    这样强迫子类override父类的抽象方法,当然,我们发现:PollsProvider类并没有报错,因为它也是抽象类。

    而具体的非抽象类如SqlPollsProvider类就非实现不可了!

    image

    我们看到DataAccess的虚方法Execute核心是调用了command对象的ExecuteNonQuery()方法用来执行SQL语句。

    但是command 对象还有ExecuteScalar()和ExecuteReader()方法,一样的我们也同样封装在该类DataAccess中。

    image

    这里要注意Reader方法中的con对象千万不能关闭,否则提供出去的IDataReader对象没有意义。CommandBehavior.CloseConnection确保了

    当调用者关闭该reader时,关联的该方法的con也会关闭。 所以调用者要记得使用后,关闭得到的这个DataReader对象。

    image

    这里值得一提的就是:存储过程输出参数的使用,该图的方法InsertPoll,经过调试,outparam必须赋予初始值。否则,运行异常。

    同样最后返回的整型用Convert.ToInt32进行强制安全转换。

    最后注意将来BLL只与PollsProvider交互,所以这里需要运用简单工厂模式来实现。

    (可以参考《大话设计模式》的image

    image

    image

    image

    同时这里也使用了image

    客户端可以如下调用了:

    image

    image

    后续是参考代码:

    DataAccess.cs

    View Code
     1 using System;
    2 using System.Collections.Generic;
    3 using System.Web;
    4 using System.Data;
    5 using System.Data.Common;
    6
    7 namespace DAL
    8 {
    9 public abstract class DataAccess //数据访问的基类
    10 {
    11 private string _connectionString = "";
    12 /// <summary>
    13 /// 数据库连接串
    14 /// </summary>
    15 protected string ConnectionString
    16 {
    17 get { return _connectionString; }
    18 set { _connectionString = value; }
    19 }
    20
    21 protected bool _enableCaching = true;
    22 /// <summary>
    23 /// 是否启用缓存
    24 /// </summary>
    25 protected bool EnableCaching
    26 {
    27 get { return _enableCaching; }
    28 set { _enableCaching = value; }
    29 }
    30
    31 private int _cacheDuration = 0;
    32 /// <summary>
    33 /// 缓存的有效期(秒)
    34 /// </summary>
    35 protected int CacheDuration
    36 {
    37 get { return _cacheDuration; }
    38 set { _cacheDuration = value; }
    39 }
    40 protected abstract IDbConnection GetConnection();
    41 protected virtual bool Execute(string procedureName, params DbParameter[] parameters)
    42 {
    43 IDbConnection con = GetConnection();
    44 IDbCommand cmd = con.CreateCommand();//通过con得到相应的cmd对象
    45 cmd.CommandText = procedureName;
    46 cmd.CommandType = CommandType.StoredProcedure;
    47 if (parameters != null)
    48 foreach (DbParameter para in parameters)
    49 cmd.Parameters.Add(para);
    50 con.Open();
    51 bool success = cmd.ExecuteNonQuery() == 1;
    52 con.Close();
    53 con.Dispose();//记得释放connecton对象
    54
    55 return success;
    56 }
    57 protected virtual int Scalar(string procedureName, params DbParameter[] parameters)
    58 {
    59 IDbConnection con = GetConnection();
    60 IDbCommand cmd = con.CreateCommand();//通过con得到相应的cmd对象
    61 cmd.CommandText = procedureName;
    62 cmd.CommandType = CommandType.StoredProcedure;
    63 if (parameters != null)
    64 foreach (DbParameter para in parameters) cmd.Parameters.Add(para);
    65 con.Open();
    66 int result = (int)cmd.ExecuteScalar();
    67 con.Close();
    68 con.Dispose();//需要释放connection占用的资源
    69 return result;
    70 }
    71
    72 protected virtual IDataReader Reader(string procedureName, params DbParameter[] parameters)
    73 {
    74 IDbConnection con = GetConnection();
    75 IDbCommand cmd = con.CreateCommand();//通过con得到相应的cmd对象
    76 cmd.CommandText = procedureName;
    77 cmd.CommandType = CommandType.StoredProcedure;
    78 if (parameters != null)
    79 foreach (DbParameter para in parameters) cmd.Parameters.Add(para);
    80 con.Open();//con是不能关闭和释放的,因为调用者需要IDataReader对象,而它工作需要这个con。
    81 IDataReader result = cmd.ExecuteReader(CommandBehavior.CloseConnection);
    82 //由调用者关闭这个result对象<调用其Close方法,这样关联的con也会同时关闭>
    83 return result;
    84 }
    85
    86 }
    87 }



    PollsProvider.cs代码如下:

    View Code
      1 using System;
    2 using System.Collections.Generic;
    3 using System.Web;
    4 using System.Data;
    5 using MyWebSite;
    6 namespace DAL
    7 {
    8 //把模块相关的实体的操作方法集中放在一起,这样比较合理
    9 public abstract class PollsProvider :DataAccess
    10 {
    11 //抽象的类提供模块相关访问的抽象方法,具体的数据访问代码交给具体的子类实现
    12 //BLL(业务逻辑层与本类进行交互,类似于简单工厂由该类提供具体的子类来完成实际的底层操作)
    13 #region
    14 private static PollsProvider _instance = null;//封装了实际的子类(数据访问类如SqlPollsProvider)
    15 static public PollsProvider Instance
    16 {
    17 //只读属性(按需加载模式<单例模式(计划生育一个孩子好)>)[用反射的技术实现]
    18 get
    19 {
    20 if (_instance == null)
    21 {
    22 //必须首先定义好web.config====创建自定义的配置节
    23 string providerTypeName = Globals.Settings.Polls.ProviderType;
    24 //"DAL.SqlClient.SqlPollsProvider";//如今保存在配置文件web.config中
    25 _instance = Activator.CreateInstance(Type.GetType(providerTypeName)) as PollsProvider;
    26
    27 } return _instance;
    28 }
    29 }
    30 #endregion
    31
    32 //很重要的初始设置 (构造方法====读取web.config相应的设置)
    33 public PollsProvider()
    34 {
    35 this.ConnectionString = Globals.Settings.Polls.ConnectionString;
    36 this.EnableCaching = Globals.Settings.Polls.EnableCaching;
    37 this.CacheDuration = Globals.Settings.Polls.CacheDuration;
    38 }
    39 //基本上是对存储过程的封装,方法名与存储过程名可以相同,存储过程的参数为方法的参数
    40 //应该用代码生成器自动完成
    41 //最后自己在进行归类处理(或者判断存储过程与哪个表关联的放一块)
    42
    43 /*
    44 * 这些方法的返回值:对象/对象集合(Get开头的方法)、
    45 * int(Insert开头的方法<例外:GetcurrentPollId方法>)、bool(非Get/Insert开头的方法)
    46 */
    47
    48 //与PollDetail有关的方法
    49 public abstract bool ArchivePoll(int pollId);
    50 public abstract bool DeletePoll(int pollId);
    51 public abstract int GetcurrentPollId();
    52 public abstract PollDetail GetPoll(int pollId);
    53 public abstract List<PollDetail> GetPolls(int includeActive, int includeArchived);
    54 public abstract int InsertPoll(string addedBy, DateTime addedDate, string questionText,
    55 bool isCurrent);
    56 public abstract bool UpdatePoll(int pollId, string questionText, bool isCurrent);
    57
    58 //与PollOptionDetail有关的方法
    59 public abstract bool DeletePollOption(int optionId);
    60 public abstract PollOptionDetail GetPollOption(int optionId);
    61 public abstract List<PollOptionDetail> GetPollOptions(int pollId);
    62 public abstract int InsertPollOption(string optionText, int pollId, string addedby,
    63 DateTime addedDate);
    64 public abstract bool InsertVote(int optionId);
    65 public abstract bool UpdatePollOption(int optionId, string optionText);
    66
    67 //下面的受保护的虚方法,允许在子类中调用
    68 //通过IDateReader对象,依次读取字段值封装成对应的实体对象/实体对象集合
    69 protected virtual PollDetail GetPollFromReader(IDataReader reader)
    70 {
    71 return new PollDetail(
    72 (int)reader["pollId"],//注意实际后台数据表的主键名称
    73 (string)reader["addedBy"],
    74 (DateTime)reader["addedDate"],
    75 (string)reader["questionText"],
    76 (bool)reader["isCurrent"], (bool)reader["isArchived"],
    77 //注意上述的写法安全,是因为后台表定义时字段不为空,所以reader有数据,
    78 //表定义字段允许为空的,需要判断是否为空的,并且转换为合理的初始值
    79 reader["archivedDate"]==DBNull.Value ?DateTime.MinValue:(DateTime)reader["archivedDate"],
    80 reader["votes"]==DBNull.Value ?0:(int)reader["votes"]
    81 );
    82 }
    83 protected virtual List<PollDetail> GetPollListFromReader(IDataReader reader)
    84 {
    85 List<PollDetail> polls = new List<PollDetail>();
    86 while (reader.Read())
    87 {
    88 polls.Add(GetPollFromReader(reader));
    89 }
    90 return polls;
    91 }
    92
    93 protected virtual PollOptionDetail GetPollOptionFromReader(IDataReader reader)
    94 {
    95 return new PollOptionDetail(
    96 (int)reader["optionId"], (string)reader["optionText"], (int)reader["votes"],
    97 (int)reader["pollId"], (string)reader["addedBy"], (DateTime)reader["addedDate"],
    98 reader["percentage"] == DBNull.Value ? 0 : (double)reader["percentage"]);
    99 }
    100 protected virtual List<PollOptionDetail> GetPollOptionListFromReader(IDataReader reader)
    101 {
    102 List<PollOptionDetail> pollOptions = new List<PollOptionDetail>();
    103 while (reader.Read()) pollOptions.Add(GetPollOptionFromReader(reader));
    104 return pollOptions;
    105 }
    106 }
    107 }
    108
    109



    SqlPollsProvider.cs如下:

    View Code
      1 using System;
    2 using System.Collections.Generic;
    3 using System.Web;
    4 using System.Data.SqlClient;
    5 using System.Data;
    6 using System.Data.Common;
    7 namespace DAL.SqlClient
    8 {
    9 /// <summary>
    10 /// 具体的SqlServer数据库的民意调查模块相关表的访问(增/删/改/查),都是调用相应的存储过程
    11 /// </summary>
    12 public class SqlPollsProvider : PollsProvider
    13 {
    14 protected override IDbConnection GetConnection()
    15 {
    16 return new SqlConnection(ConnectionString);
    17 }
    18
    19 public override bool ArchivePoll(int pollId)
    20 {
    21 return Execute("ArchivePoll",
    22 new SqlParameter[] { new SqlParameter("@pollId", pollId) });
    23 }
    24
    25 public override bool DeletePoll(int pollId)
    26 {
    27 return Execute("DeletePoll",
    28 new SqlParameter[] { new SqlParameter("@pollId", pollId) });
    29 }
    30
    31 public override int GetcurrentPollId()
    32 {
    33 return Scalar("GetCurrentPollID");
    34 }
    35
    36 public override PollDetail GetPoll(int pollId)
    37 {
    38 IDataReader reader=
    39 Reader("GetPoll", new SqlParameter[] { new SqlParameter("@PollID",pollId)});
    40 reader.Read();
    41 PollDetail result= GetPollFromReader(reader);
    42 reader.Close();
    43 return result;
    44 }
    45
    46 public override List<PollDetail> GetPolls(int includeActive, int includeArchived)
    47 {
    48 IDataReader reader = Reader("GetPolls",
    49 new SqlParameter[] {
    50 new SqlParameter("@IncludeActive",includeActive),
    51 new SqlParameter("@IncludeArchived",includeArchived)});
    52 List<PollDetail> result = GetPollListFromReader(reader);
    53 reader.Close();
    54 return result;
    55 }
    56
    57 public override int InsertPoll(string addedBy, DateTime addedDate, string questionText, bool isCurrent)
    58 {
    59 SqlParameter outparam = new SqlParameter("@PollID", 0);//不赋值0,报初始值错误
    60 outparam.Direction = ParameterDirection.Output;
    61
    62 Execute("InsertPoll",
    63 new SqlParameter[] {
    64 new SqlParameter("@AddedBy",addedBy),
    65 new SqlParameter("@AddedDate",addedDate),
    66 new SqlParameter("@QuestionText",questionText),
    67 new SqlParameter("@IsCurrent",isCurrent),
    68 outparam
    69 });
    70 //return (int)outparam.Value;转换无效
    71 return Convert.ToInt32(outparam.Value);
    72 }
    73
    74 public override bool UpdatePoll(int pollId, string questionText, bool isCurrent)
    75 {
    76 return Execute("UpdatePoll", new SqlParameter[] {
    77 new SqlParameter("@PollID",pollId),
    78 new SqlParameter("@QuestionText",questionText),
    79 new SqlParameter("@IsCurrent",isCurrent)});
    80 }
    81
    82 public override bool DeletePollOption(int optionId)
    83 {
    84 return Execute("DeletePollOption", new SqlParameter[] {
    85 new SqlParameter("@OptionID",optionId) });
    86 }
    87
    88 public override PollOptionDetail GetPollOption(int optionId)
    89 {
    90 IDataReader reader = Reader("GetPollOption", new SqlParameter[] {
    91 new SqlParameter("@OptionID",optionId) });
    92 reader.Read();
    93 PollOptionDetail result= GetPollOptionFromReader(reader);
    94 reader.Close();
    95 return result;
    96 }
    97
    98 public override List<PollOptionDetail> GetPollOptions(int pollId)
    99 {
    100 IDataReader reader = Reader("GetPollOptions", new SqlParameter[] {
    101 new SqlParameter("@PollID",pollId)});
    102 List<PollOptionDetail> result= GetPollOptionListFromReader(reader);
    103 reader.Close();
    104 return result;
    105 }
    106
    107 public override int InsertPollOption(string optionText, int pollId, string addedby, DateTime addedDate)
    108 {
    109 SqlParameter outparam = new SqlParameter("@OptionID", 0);//不赋值0,报初始值错误
    110 outparam.Direction = ParameterDirection.Output;
    111 Execute("InsertPollOption", new SqlParameter[] {
    112 new SqlParameter("@OptionText",optionText ),
    113 new SqlParameter("@PollID",pollId),
    114 new SqlParameter("@AddedDate",addedDate),
    115 new SqlParameter("@AddedBy",addedby),outparam
    116 });
    117 return Convert.ToInt32(outparam.Value);
    118 }
    119
    120 public override bool InsertVote(int optionId)
    121 {
    122 return Execute("InsertVote", new SqlParameter[] { new SqlParameter("@OptionID", optionId) });
    123 }
    124
    125 public override bool UpdatePollOption(int optionId, string optionText)
    126 {
    127 return Execute("UpdatePollOption", new SqlParameter[] {
    128 new SqlParameter("@OptionID",optionId),
    129 new SqlParameter ("@OptionText",optionText) });
    130 }
    131 }
    132 }



    子曰:“学而时习之,不亦说乎!有朋自远方来,不亦乐乎!人不知而不愠,不亦君子乎!”
    当来之世,经道灭尽,我以慈悲哀愍,特留此经止住百岁。其有众生,值斯经者,随意所愿,皆可得度。(南无阿弥陀佛)
    道可道,非常道;名可名,非常名。
    我常说,一个国家,一个民族,亡国都不怕,最可怕的是一个国 家和民族自己的根本文化亡掉了,这就会沦为万劫不复,永远不会翻身。---南怀瑾<国学大师>
    传统文化论坛网站
  • 相关阅读:
    艾伦 Visual Studio 批量自动化代码操作工具-VS插件发布
    Visual Studio 打开解决方案后 弹出框显示 "正在打开文件..." 迟迟没反应 的解决方法
    小米抢购神器-开放源码
    python语法
    python运算符
    python字符串
    python多线程,多进程编程。
    subprocess模块
    jenkins
    python中的lxml模块
  • 原文地址:https://www.cnblogs.com/jiangguanghe/p/2328618.html
Copyright © 2020-2023  润新知