博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

小菜梦游Discuz!NT (第八篇 数据层设计4)

Posted on 2008-07-19 21:23  a-peng  阅读(1108)  评论(0编辑  收藏  举报

话说小菜已经做到了支持多数据库的大体设计了.

老大来看小菜,看小菜的进步如何.

看到小菜的设计之后,老大给予了一定的肯定,但还是少不了一些意见.

1)老大对小菜的ForumManage的代码极其不满意.

 

public class ForumFactory
    
{
        
        
public static IForumManage CreateForumManage()
        
{
            IForumManage forumManage 
= null;
            
switch (BaseConfigFileManager.GetDbType)
            

                
case "Access" :
                    forumManage 
= (IForumManage)Assembly.Load("Discuz.Data.Access").CreateInstance("Discuz.Data.Access.ForumManage");
                    
break;
                
case "SqlServer" :
                    forumManage 
= (IForumManage)Assembly.Load("Discuz.Data.SqlServer").CreateInstance("Discuz.Data.SqlServer.ForumManage");
                    
break;
                
default :
                    
throw new Exception("暂不支持其它数据库类型");
            }


            
return forumManage;
        }

}

 

第一点: 静态CreateForumManage()函数,小菜既然没有考虑到IForumManage每用一次就要被实例化一次.

第二点:代码是否冗余.

小菜对代码进行了改进

using System;
using System.Reflection;
using Discuz.Config;

namespace Discuz.Data
{
    
public class ForumFactory
    
{
        
private static IForumManage m_forumManage = null;
        
        
public static IForumManage CreateForumManage()
        
{
            
if (m_forumManage == null)
            
{
                
try
                
{
                    
string assembly = string.Format("Discuz.Data.{0}", BaseConfigFileManager.GetDbType);
                    
string typeName = string.Format("Discuz.Data.{0}.ForumManage", BaseConfigFileManager.GetDbType);
                    m_forumManage 
= (IForumManage)Assembly.Load(assembly).CreateInstance(typeName);
                }

                
catch
                
{
                    
throw new Exception("请检查DNT.config中Dbtype节点数据库类型是否正确,例如:SqlServer、Access");
                }

            }


            
return m_forumManage;
        }

    }

}

恩,不错,小菜可教也.

老大继续说到,小菜啊,你看ForumFactory在我们的代码中是独一无二的,而且我们希望它一身只被构造一次.

小菜心领神会(老大的意思是说,这个类更合适使用单件模式来使用.)

using System;
using System.Reflection;
using Discuz.Config;

namespace Discuz.Data
{
    
public class ForumFactory
    
{
        
private static IForumManage m_instance = null;
        
private static object m_lock = new object();

        
private ForumFactory()
        
{}

        
static ForumFactory()
        
{
            GetProvider();
        }


        
private static void GetProvider()
        
{
            
try
            
{
                m_instance 
= (IForumManage)Activator.CreateInstance(Type.GetType(string.Format("Discuz.Data.{0}.ForumManage, Discuz.Data.{0}",BaseConfigFileManager.GetDbType),false,true));
            }

            
catch
            
{
                
throw new Exception("请检查DNT.config中Dbtype节点数据库类型是否正确,例如:SqlServer、Access");
            }

        }


        
public static IForumManage GetInstance()
        
{
            
if (m_instance == null)
            
{
                
lock (m_lock)
                
{
                    
if (m_instance == null)
                    
{
                        GetProvider();
                    }

                }

            }

            
return m_instance;
        }

    }

}

那我们的调用方式也要从IForumManage forum = ForumFactory.CreateForumManage();

改为IForumManage forum = ForumFactory.CetInstance();

 

其实单件的实现方式有三种,一,二两种比较常见,第三种比较少见,不过却是最好的,上面的代码是第一种.

大家可以参考怪怪的一篇随笔: 对Singleton的实现方法做一个总结

或者直接看这篇 http://www.yoda.arachsys.com/csharp/singleton.html (写的更细心,不过是英文的)

 

对噢.老大的意见还没说完呢! :)

2)小菜啊,你看代码中的SqlHelper与AccessHelper代码重复太多,而且完成的功能基本类似,所以你得考虑下重构下他们俩兄弟.(老大还给了小菜一个提示: System.Data.Common中已经为我们提供了帮助,可以去看看)

 

小菜重新打开了System.Data.Common的源码. (发现了如下的代码组织,这不是抽象工厂模式吗.)

图画的不是很好看,大家将就下吧, :)

 

public abstract class DbProviderFactory {
    
    
protected DbProviderFactory() {
    }

    
    
public virtual DbCommand CreateCommand() 
            
return null;
        }


    
public virtual DbConnection CreateConnection() 
            
return null
        }

    
    
public virtual DbParameter CreateParameter() 
            
return null;
        }

}


public sealed class SqlClientFactory : DbProviderFactory
{
    
public static readonly SqlClientFactory Instance = new SqlClientFactory();

    
private SqlClientFactory() {
    }


    
public override DbCommand CreateCommand() {
            
return new SqlCommand(); 
        }


    
public override DbConnection CreateConnection() 
            
return new SqlConnection();
        }
 

    
public override DbParameter CreateParameter() {
            
return new SqlParameter();
        }

}


public sealed class OleDbFactory : DbProviderFactory
{
    
public static readonly OleDbFactory Instance = new OleDbFactory();

    
private OleDbFactory() {
    }


    
public override DbCommand CreateCommand() {
            
return new OleDbCommand(); 
        }


    
public override DbConnection CreateConnection() 
            
return new OleDbConnection();
        }
 

    
public override DbParameter CreateParameter() {
            
return new OleDbParameter();
        }

}

 

看来老大的提示确实给了小菜不小的帮助.

小菜开始拿起手头的SqlHelper与AccessHelper准备改造了.

using System;
using System.Web;
using System.Data;
using System.Data.Common;
using System.Data.OleDb;
using System.Data.SqlClient;
using Discuz.Config;

namespace Discuz.Data
{
    
public abstract class DbHelper
    
{
        
private static string m_connString;  
        
private static DbProviderFactory m_factory = null;

        
static DbHelper()
        
{
            
switch (BaseConfigFileManager.GetDbType)
            
{
                
case "Access":
                    m_connString 
= "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
            HttpContext.Current.Server.MapPath(
"~/database/access_db.config"); //Access数据库连接串
                    m_factory = OleDbFactory.Instance;
                    
break;
                
case "SqlServer":
                    m_connString 
= "Data Source=(local);User ID=sa;Password=password;"+
                            
"Initial Catalog=discuz;Pooling=true"//Sql数据库连接串
                    m_factory = SqlClientFactory.Instance;
                    
break;
                
default:
                    
throw new Exception("暂不支持其它数据库类型");
            }

        }


        
private static DbProviderFactory Factory
        
{
            
get
            
{
                
return m_factory;
            }

        }


        
/// <summary>
        
/// 执行一个DbCommand返回一个记录集
        
/// </summary>
        
/// <remarks>
        
/// 例如:  
        
/// DbDataReader r = ExecuteReader(CommandType.Text, "select id,name from dnt_forums", null);
        
/// </remarks>
        
/// <param name="cmdType">DbCommand类型</param>
        
/// <param name="cmdText">DbCommand文本</param>
        
/// <param name="cmdParms">DbCommand的参数DbParameter</param>
        
/// <returns>一个包含记录的DbDataReader</returns>

        public static DbDataReader ExecuteReader(CommandType cmdType, string cmdText, params DbParameter[] cmdParms)
        
{
            DbCommand cmd 
= Factory.CreateCommand();
            DbConnection conn 
= Factory.CreateConnection();
            conn.ConnectionString 
= m_connString;

            
try
            
{
                PrepareCommand(cmd, conn, cmdType, cmdText, cmdParms);
                DbDataReader rdr 
= cmd.ExecuteReader(CommandBehavior.CloseConnection);
                cmd.Parameters.Clear();
                
return rdr;
            }

            
catch (Exception ex)
            
{
                conn.Close();
                
throw ex;
            }

        }


        
/// <summary>
        
/// 执行一个DbCommand并返回第一条记录的第一列的值
        
/// </summary>
        
/// <remarks>
        
/// 例如:  
        
/// SqlParameter parm = new SqlParameter("@fid", SqlDbType.Int, 4);
        
/// parm.Value = fid;
        
/// Object obj = ExecuteScalar(CommandType.Text, "select name from dnt_forums where fid=@fid", parm);
        
/// 或者
        
/// OleDbParameter parm = new OleDbParameter("@fid", OleDbType.Integer, 4);
        
/// parm.Value = fid;
        
/// Object obj = ExecuteScalar(CommandType.Text, "select name from dnt_forums where fid=@fid", parm);

        
/// </remarks>
        
/// <param name="cmdType">DbCommand类型</param>
        
/// <param name="cmdText">DbCommand文本</param>
        
/// <param name="cmdParms">DbCommand的参数DbParameter</param>
        
/// <returns>返回第一条记录的第一列的值</returns>

        public static object ExecuteScalar(CommandType cmdType, string cmdText, params DbParameter[] cmdParms)
        
{
            DbCommand cmd 
= Factory.CreateCommand();

            
using (DbConnection conn = Factory.CreateConnection())
            
{
                conn.ConnectionString 
= m_connString;
                PrepareCommand(cmd, conn, cmdType, cmdText, cmdParms);
                
object val = cmd.ExecuteScalar();
                cmd.Parameters.Clear();
                
return val;
            }

        }


        
/// <summary>
        
/// 执行一个DbCommand返回受该DbCommand影响的行数
        
/// </summary>
        
/// <remarks>
        
/// 例如:  
        
/// SqlParameter parm = new SqlParameter("@fid", SqlDbType.Int, 4);
        
/// parm.Value = fid;
        
/// int result = ExecuteNonQuery(CommandType.Text, "delete from dnt_forums where fid=@fid", parm);
        
/// 或者
        
/// OleDbParameter parm = new OleDbParameter("@fid", OleDbType.Integer, 4);
        
/// parm.Value = fid;
        
/// int result = ExecuteNonQuery(CommandType.Text, "delete from dnt_forums where fid=@fid", parm);
        
/// </remarks>
        
/// <param name="cmdType">DbCommand类型</param>
        
/// <param name="cmdText">DbCommand文本</param>
        
/// <param name="cmdParms">DbCommand的参数DbParameter</param>
        
/// <returns>返回受DbCommand影响的行数</returns>

        public static int ExecuteNonQuery(CommandType cmdType, string cmdText, params DbParameter[] cmdParms)
        
{
            DbCommand cmd 
= Factory.CreateCommand();

            
using (DbConnection conn = Factory.CreateConnection())
            
{
                conn.ConnectionString 
= m_connString;
                PrepareCommand(cmd, conn, cmdType, cmdText, cmdParms);
                
int val = cmd.ExecuteNonQuery();
                cmd.Parameters.Clear();
                
return val;
            }

        }

        
        
/// <summary>
        
/// 准备一个DbCommand用来执行
        
/// </summary>
        
/// <param name="cmd">DbCommand对象</param>
        
/// <param name="conn">DbConnection对象</param>
        
/// <param name="cmdType">DbCommand类型</param>
        
/// <param name="cmdText">DbCommand文本</param>
        
/// <param name="cmdParms">DbCommand的参数DbParameter</param>

        private static void PrepareCommand(DbCommand cmd, DbConnection conn, CommandType cmdType, string cmdText, DbParameter[] cmdParms)
        
{

            
if (conn.State != ConnectionState.Open)
                conn.Open();

            cmd.Connection 
= conn;
            cmd.CommandText 
= cmdText;
            cmd.CommandType 
= cmdType;

            
if (cmdParms != null)
            
{
                
foreach (DbParameter parm in cmdParms)
                    cmd.Parameters.Add(parm);
            }

        }

    }

}

 

看来小菜的工作并不会太难,因为有了DbProviderFactory,SqlClientFactory,OleDbFactory的帮助,当然还有个静态构造函数.

 

将下来,当然是使用它啦.还是老例子,那两个.

1)遍历dnt_forums表,输出fid,name

2)<a href="GetForumName.aspx?fid=1">取得版块名称</a>

IForumManage接口

using System;
using System.Data;

namespace Discuz.Data
{
    
public interface IForumManage
    
{
        IDataReader GetForums();
        
string GetForumName(int fid);
    }

}

 

Sql数据库实现

using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;

namespace Discuz.Data.SqlServer
{
    
public class ForumManage : IForumManage
    
{
        
public IDataReader GetForums()
        
{
            
string cmdText = "select fid,name from dnt_forums";
            
return DbHelper.ExecuteReader(CommandType.Text, cmdText, null);
        }


        
public string GetForumName(int fid)
        
{
            
string cmdText = "select name from dnt_forums where fid=@fid";
            SqlParameter parm 
= new SqlParameter("@fid", SqlDbType.Int, 4);
            parm.Value 
= fid;

            
return DbHelper.ExecuteScalar(CommandType.Text, cmdText, parm).ToString();
        }

    }

}

Access数据库实现

 

using System;
using System.Data;
using System.Data.Common;
using System.Data.OleDb;

namespace Discuz.Data.Access
{
    
public class ForumManage : IForumManage
    
{
        
public IDataReader GetForums()
        
{
            
string cmdText = "select fid,name from dnt_forums";
            
return DbHelper.ExecuteReader(CommandType.Text, cmdText, null);
        }


        
public string GetForumName(int fid)
        
{
            
string cmdText = "select name from dnt_forums where fid=@fid";
            OleDbParameter parm 
= new OleDbParameter("@fid", OleDbType.Integer, 4);
            parm.Value 
= fid;

            
return DbHelper.ExecuteScalar(CommandType.Text, cmdText, parm).ToString();
        }

    }

}

 

接下来是什么呢?当然是使用下它们啊. 遍历dnt_forums表

 

using System;
using System.Data;
using Discuz.Data;

public partial class _Default : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    
{
        IForumManage forum 
= ForumFactory.CetInstance();

        IDataReader reader 
= forum.GetForums();
        
while (reader.Read())
        
{
            Response.Write(reader[
"fid"]);
            Response.Write(reader[
"name"]);

        }

    }

}

 

按fid取版块名称

 

using System;
using System.Web;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using Discuz.Data;

public partial class GetForumName : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    
{
        
int fid = Convert.ToInt32(Request.Params["fid"]);//获取传入参数fid
        IForumManage forum = ForumFactory.CetInstance();

        Response.Write(forum.GetForumName(fid));
    }

}

 

现在如果是使用Sql数据库就将DNT.config中的DbType结点修改为<DbType>SqlServer</DbType>

现在如果是使用Access数据库就将DNT.config中的DbType结点修改为<DbType>Access</DbType>

 

咦,看那头的小菜在沉思.在沉思什么呢? 小菜在想还有哪些不足,还有哪些重复.(这可是个好兆头啊.我们设计一个阶段最好就要进行一次代码重构.)

 

1)问题1: 数据库连接字符串,我们在配置处理部份中已经将它放在了DNT.config的<DbConnectString></DbConnectString>结点中

所以我们可以使用BaseConfigFileManager.GetDbConnectString来替换数据库连接字符串

"Data Source=(local);User ID=sa;Password=password;Initial Catalog=discuz;Pooling=true"

"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" +
   HttpContext.Current.Server.MapPath("~/database/access_db.config")

当然Access数据库连接字符串,我们可以在安装时,将 "~/database/access_db.config"

转换成绝对路径放入DNT.config中

 

代码现在为private static string m_connString = BaseConfigFileManager.GetDbConnectString;

 

2)问题2:  sql语句"select fid,name from dnt_forums"中表的dnt_这个前缀,我们在配置处理部份中也已经将它放在了DNT.config的<TablePrefix></TablePrefix>结点中

所以我们可以用string.Format("select fid,name from {0}forums", BaseConfigFileManager.GetTablePrefix);

来替换上面的sql语句了.


3)问题3: 如果添加了MySql数据库的支持,那么DbHelper的代码,我们又需要修改为

namespace Discuz.Data
{
    
public abstract class DbHelper
    
{
        
private static string m_connString = BaseConfigFileManager.GetDbConnectString; //数据库连接串
        private static DbProviderFactory m_factory = null;

        
static DbHelper()
        
{
            
switch (BaseConfigFileManager.GetDbType)
            
{
                
case "Access":
                    m_factory 
= OleDbFactory.Instance;
                    
break;
                
case "SqlServer":
                    m_factory 
= SqlClientFactory.Instance;
                    
break;
                
case "MySql":
                    m_factory 
= MySqlClientFactory.Instance;
                    
break;
                
default:
                    
throw new Exception("暂不支持其它数据库类型");
            }

        }

        其它略,同上
    }

}

这个需求是成立的,因为MySql做为现在最流行的数据库之一,已经被Discuz!NT支持.

不过需要添加它的组件MySql.Data.dll

然后using MySql.Data.MySqlClient; 便可支持上面的MySqlClientFactory.Instance

 

如果支持Oracle数据库呢?,看来switch又得加长.......但可喜的是数据库流行的种类并不算太多.所以switch也不可能太长.

 

但还有一点值得我们注意的:

Access和MySql不支持存储过程,Sql支持

Access和MySql不支持数据库备份,Sql支持

Access和Sql不支持优化,MySql支持

Access和MySql不支持全文搜索,Sql支持

等等,,数据库之前的差异,那么我们为何不顺水推舟呢.把上面的switch(){....}这个毒瘤一块干掉呢.

 

那下篇咱们就来动动手术吧.

 

休息,,休息