接着数据访问层DAL的再次重构_2_模块的自定义设置节我们继续实现,到这里,开始后台编码,经历了建立数据库、建表、存储过程、web.config的自定义配置节点后,我们来实现数据访问层的编码。
首先:用OOP方式来映射后台的表Polls和PollOptions。分别命名为PollDetail和PollOptionDetail简单实体类。
注意一些转换映射的事项:
- 简单实体就是对表的列进行封装。映射为对应的属性时注意赋予初始值。
(如日期字段的复制根据需要选择 DateTime.Now 和 DateTime.MinValue、DateTime.MaxValue)
- 构造方法需要重载(添加带参数的构造方法,实现对需要的属性赋值)
- 私有字段的命名一般以 _ 下划线开头,采用骆驼命名法
- 不管实际的数据库的表的主键字段是什么,这里都用Id属性名统一
- 可能会增加自己的属性[如Votes](表Polls并没有该列,但是获取记录的存储过程确提供了<详情见DAL的再次重构_1>)
这里的命名空间定义为DAL(通常是DAL.Entity<根文件夹名层次匹配>)。实际上简单实体类的编码通常用代码生成器自动完成。(如CodeSmith工具)
PollOptionDetail.cs代码如下:(增加了Percentage属性:调查选项占总投票数的百分比)
上面的构造方法都重载了(增加了带参数的功能,便于初始化。)
我们还是考虑使用多种类型数据库的可能,比如使用Oracle数据库/SqlServer数据库/DB2数据库等等。这是通过不同的数据提供程序[Provider]来访问的。
比如获取的数据来源类型不同,匹配的数据源控件就不同,如(SqlDataSource1/AccessDataSource1/XmlDataSource1等等。。。)
尽管名称不同,但都是DataSource数据源控件,根据你选择的不同,就自动匹配不同的具体数据源控件。
我们这里不管是哪种类型的数据库,对表Polls和表PollOptions的访问都是增删改查(CRUD),直白说就是那些存储过程所实现的功能。所以这里建立抽象类PollsProvider来实现这些功能封装。具体的实现代码则交给其后的子类SqlPollsProvider来完成。(AccessPollsProvider的代码省略)
建立抽象的父类:PollsProvider用来把模块相关的实体的操作方法集中放在一起,这样比较合理紧凑。
BLL(业务逻辑层与本类进行交互,类似于简单工厂由该类提供具体的子类来完成实际的底层操作)
注意事项:
- 基本上是对存储过程的封装,方法名与存储过程名可以相同,存储过程的参数为方法的参数
- 命名规范便于以后代码生成器的实现
实际上而言:应该还有个更高的父类:DataAccess,然后:PollsProvider:DataAccess,因为网站不止一个模块,会出现更多的XXXProvider,它们的通用操作就可以放在父类DataAccess中,以继承的方式拥有。而且每个模块有各自的缓存时间、是否激活缓存等等设置。
所以这里添加DataAccess类。
这样修改PollsProvider构造方法,实现从DataAccess继承的属性的初始化赋值。(这些都是通过读取web.config自定义配置的部分<详见自定义配置节>)
接下来实现具体的子类SqlPollsProvider实现对SqlServer数据库的访问。
这里我们就发现这两个方法出现重复代码,需要抽取出来重构成新方法。我认为原则就是找到变和不变的地方。(变的地方用参数灵活处理,不便的地方就用代码方式固定下来)。仔细分析一下就得知:实质上就是调用SqlCommand对象执行存储过程。(而存储过程的名成和参数是变化的),那么提取的重构方法如下:
接着编写下面的方法,如下图:
虽然此时,并没有出现重复的代码情况,但是根据前面的经验,我们这里也有能力进行代码重构了,改动如下:
该图中的第二个红色框框实现的是从SqlDataReader中读取当前记录各列的数据,然后初始化为实体对象PollDetail(调用了带参数的构造方法)
虽然没有编写后面的GetPolls方法,但是可以肯定这段代码肯定会重复的。所以提取出来,改动后如下:
我们可以考虑到如果实现AccessPollsProvider的这一部分时,会出现如下的情况:
其实不难发现:AccessPollsProvider和SqlPollsProvider的私有方法GetPollFromReader重复,仅仅参数不同:但是它们都实现了接口IDataReader。所以提取到父类PollsProvider中。进一步,我们接着也封装了后面的三个受保护的GetXXXX方法。(见下图)
这样原有的子类中的私有方法GetPollFromReader就可以去掉了。
我们发现上图的这两个红色框框部分能否重构呢?
其实也是可以的,我们知道通过Connection对象的CreateCommand 方法能得到对应的Command对象,而SqlParameter与OleDbParameter的父类都是同一个DbParameter。而Connection的父类都是DbConnection
那么我们实现提取到父类(DataAccess)中(这里应该叫祖先类了) ,仔细想想就会明白放在DataAccess类中更通用。为了区别,我们把这个方法定义为Execute。
对应的子类调用的代码如下:
感觉比起原来的ExecuteCmd方法反而多了一个Connection对象参数,能不能再方便些了?
其实还是可以实现的。
(来源请参考……
……)
回到我们的这个主题上来,运用模板方法模式,我们继续改善:
这样强迫子类override父类的抽象方法,当然,我们发现:PollsProvider类并没有报错,因为它也是抽象类。
而具体的非抽象类如SqlPollsProvider类就非实现不可了!
我们看到DataAccess的虚方法Execute核心是调用了command对象的ExecuteNonQuery()方法用来执行SQL语句。
但是command 对象还有ExecuteScalar()和ExecuteReader()方法,一样的我们也同样封装在该类DataAccess中。
这里要注意Reader方法中的con对象千万不能关闭,否则提供出去的IDataReader对象没有意义。CommandBehavior.CloseConnection确保了
当调用者关闭该reader时,关联的该方法的con也会关闭。 所以调用者要记得使用后,关闭得到的这个DataReader对象。
这里值得一提的就是:存储过程输出参数的使用,该图的方法InsertPoll,经过调试,outparam必须赋予初始值。否则,运行异常。
同样最后返回的整型用Convert.ToInt32进行强制安全转换。
最后注意将来BLL只与PollsProvider交互,所以这里需要运用简单工厂模式来实现。
客户端可以如下调用了:
后续是参考代码:
DataAccess.cs
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代码如下:
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如下:
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 }
当来之世,经道灭尽,我以慈悲哀愍,特留此经止住百岁。其有众生,值斯经者,随意所愿,皆可得度。(南无阿弥陀佛)
道可道,非常道;名可名,非常名。
我常说,一个国家,一个民族,亡国都不怕,最可怕的是一个国 家和民族自己的根本文化亡掉了,这就会沦为万劫不复,永远不会翻身。---南怀瑾<国学大师>
传统文化论坛网站