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

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

Posted on 2008-07-16 16:50  a-peng  阅读(901)  评论(2编辑  收藏  举报

夏天的天气就是容易犯困.你看,小菜已经趴在桌上睡着了.
小菜又来回到梦中那熟悉的地方:Discuz公司.
老大看到小菜,说到:小菜啊,孺鸟可教也,现在我给你分配新任务,你收拾一下东西,到数据访问层小组那去,那里正缺人呢.

小菜到数据访问层小组后,与小组成员进行了沟通了解到如下信息.
Discuz!NT2.0 将支持多种数据库 Access,SqlServer,MySql
所以我们设计出来的数据库层架构要合理,而且要方便调用,始终坚持各种数据库的访问方式一致性.
(看来这次任务不简单啊,老大把小菜当大菜使用啊.)

小菜有点而心虚了,好久没有写过访问数据库操作了,都快忘的一干二净了.
不过俺不怕.因为俺有SDK2.0文档.赶紧回去复习一下.
小菜打算先针对Sql数据库进行设计然后再将其扩展成支持多种数据库.
小菜始终认为,心急吃不了热豆腐,我们要步步为营,不管是现在的敏捷开发,还是单元测试,其实讲的都是这个道理.

小菜从小组其它成员那拿到了已经设计好的SQL数据库discuz(小菜也希望在之后的文章中分析该数据库的设计,因为很经典)
 表:dnt_forums(论坛版块信息)        这个表被小菜简化了,因为这样更能说明问题

序号

字段名

类型

长度

说明

1

fid

int

4

论坛fid

2

name

nchar

50

论坛名称

表:dnt_forums(论坛版块信息)已有数据

fid

name

1

版块1

2

版块2

小菜准备先拿这个表练练手感,找回往日的自信.(小菜打算一步一步封装)

1)小菜第一招    管你三七二十一
也就是直接在需要访问数据库的地方,直接访问数据库,丝毫没有封装.代码重复量很大.
小菜打算输出dnt_forums表中的内容

using System;
using System.Web;
using System.Data;
using System.Data.SqlClient;

public partial class _Default : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    
{
        
string cmdText = "select fid,name from dnt_forums";
        
string connString = "Data Source=(local);" +
            
"User ID=sa;Password=password;Initial Catalog=discuz;Pooling=true";

        SqlConnection conn 
= new SqlConnection(connString);
        
if (conn.State != ConnectionState.Open)
            conn.Open();  
//如果连接状态不是打开,则打开连接

        SqlCommand cmd 
= new SqlCommand(cmdText, conn);
        SqlDataReader reader 
= cmd.ExecuteReader(CommandBehavior.CloseConnection);
        
//在关闭DataReader时,关联的Connection也将被关闭

        
while (reader.Read())//循环读取版块名称
        {
            Response.Write(reader[
"fid"]);
            Response.Write(reader[
"name"]);
        }


        reader.Close();
//关闭DataReader
        reader.Dispose();//释放资源

        
if (conn.State != ConnectionState.Closed)//如果连接状态不是关闭,则关闭,并释放资源
        {
            conn.Close();
            conn.Dispose();
        }

    }

}

程序输出:    1版块1 2版块2
虽然是完成了任务,但是如果试想一下,以上代码在我们的论坛中遍地开花.那将是何等的恐怖.
不过还是有一点值得表扬的,代码习惯不错,记住了,用过的要关闭与释放,如SqlConnection等

2)小菜第二招    人老了记性不好怎么办.
合理的利用using,as,is,params会有意想不到的效果.    
小菜最近记性不太好,打开了SqlConnecion等,老是放了关.有没有比较好的方法呢?
有: using 英文就是使用的意思,使用过后便会自己关闭与释放所使用的资源.

using System;
using System.Web;
using System.Data;
using System.Data.SqlClient;

public partial class _Default : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    
{
        
string cmdText = "select fid,name from dnt_forums";
        
string connString = "Data Source=(local);" +
            
"User ID=sa;Password=password;Initial Catalog=discuz;Pooling=true";

        
using (SqlConnection conn = new SqlConnection(connString))
        
{
            
if (conn.State != ConnectionState.Open)
                conn.Open();  
//如果连接状态不是打开,则打开连接

            SqlCommand cmd 
= new SqlCommand(cmdText, conn);

            
using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
            
{
                
while (reader.Read())//循环读取
                {
                    Response.Write(reader[
"fid"]);
                    Response.Write(reader[
"name"]);
                }

            }


        }

    }

}

程序输出:    1版块1 2版块2
小菜刚在沾沾自喜,这时老大走了过来,看看小菜有没有进步.看到小菜的代码说到.
小菜啊!如果传入新页面一个fid,让你取出版块名称.你打算怎么做啊?
<a href="GetForumName.aspx?fid=1">取出版块名称的页面</a>
小菜心中暗喜.这有什么难的,要在老大面前展示展示.

using System;
using System.Web;
using System.Data;
using System.Data.SqlClient;

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

        
string cmdText = "select name from dnt_forums where fid=@fid";
        
string connString = "Data Source=(local);" +
            
"User ID=sa;Password=password;Initial Catalog=discuz;Pooling=true";

        
using (SqlConnection conn = new SqlConnection(connString))
        
{
            
if (conn.State != ConnectionState.Open)
                conn.Open();  
//如果连接状态不是打开,则打开连接

            SqlParameter parm 
= new SqlParameter("@fid", SqlDbType.Int, 4);//设置Sql参数fid
            parm.Value = fid;

            SqlCommand cmd 
= new SqlCommand(cmdText, conn);
            cmd.Parameters.Add(parm);

            
string name = cmd.ExecuteScalar().ToString();

            Response.Write(name);
        }

    }

}

程序正常输出:版块1
小菜心中又想不对啊,自言自语到,代码重复好多,这不是和之前的配置处理时遇到的问题一样嘛.
这才想到,看来老大是在考我.    老大满意的走了..

3)小菜第三招    小菜小封装
现在很多的开源代码如果没有使用ORM,基本上都会看到 数据库访问帮助类如SqlHelper.
小菜也打算自己弄个SqlHelper来封装对Sql数据库的访问操作.看来我们的代码质量又得到了一定的提高.那我们就继续努力吧,把SqlHelper改造成功能强大的Sql数据库操作类吧.

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

namespace Discuz.Data
{
    
public class SqlHelper
    
{
        
private static string m_connString = "Data Source=(local);" +
            
"User ID=sa;Password=password;Initial Catalog=discuz;Pooling=true"//Sql数据库连接字符串

        
/// <summary>
        
/// 执行一个SqlCommand返回一个记录集
        
/// </summary>
        
/// <remarks>
        
/// 例如:  
        
/// SqlDataReader r = ExecuteReader(CommandType.Text, "select id,name from dnt_forums", null);
        
/// </remarks>
        
/// <param name="cmdType">SqlCommand类型 如存储过程或Sql文本命令</param>
        
/// <param name="cmdText">SqlCommand文本 如存储过程名称或Sql语句</param>
        
/// <param name="cmdParms">SqlCommand的参数SqlParameters</param>
        
/// <returns>一个包含记录的SqlDataReader</returns>

        public static SqlDataReader ExecuteReader(CommandType cmdType, string cmdText, params SqlParameter[] cmdParms)
        
{
            SqlCommand cmd 
= new SqlCommand();
            SqlConnection conn 
= new SqlConnection(m_connString);

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

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

        }


        
/// <summary>
        
/// 执行一个SqlCommand并返回第一条记录的第一列的值
        
/// </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);
        
/// </remarks>
        
/// <param name="cmdType">SqlCommand类型 如存储过程或Sql文本命令</param>
        
/// <param name="cmdText">SqlCommand文本 如存储过程名称或Sql语句</param>
        
/// <param name="cmdParms">SqlCommand的参数SqlParameters</param>
        
/// <returns>返回第一条记录的第一列的值</returns>

        public static object ExecuteScalar(CommandType cmdType, string cmdText, params SqlParameter[] cmdParms)
        
{
            SqlCommand cmd 
= new SqlCommand();

            
using (SqlConnection conn = new SqlConnection(m_connString))
            
{
                PrepareCommand(cmd, conn, cmdType, cmdText, cmdParms);
                
object val = cmd.ExecuteScalar();
                cmd.Parameters.Clear();
                
return val;
            }

        }


        
/// <summary>
        
/// 准备一个SqlCommand用来执行
        
/// </summary>
        
/// <param name="cmd">SqlCommand对象</param>
        
/// <param name="conn">SqlConnection对象</param>
        
/// <param name="cmdType">SqlCommand类型 如存储过程或Sql文本命令</param>
        
/// <param name="cmdText">SqlCommand文本 如存储过程名称或Sql语句</param>
        
/// <param name="cmdParms">SqlCommand的参数SqlParameters</param>

        private static void PrepareCommand(SqlCommand cmd, SqlConnection conn, CommandType cmdType, string cmdText, SqlParameter[] cmdParms)
        
{

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

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

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

        }


    
    }

}

那接下来,就让我们来享受这个SqlHelper帮助类的成果吧.调用它吧.
遍历dnt_forums,输出fid和name

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

public partial class _Default : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    
{
        
string cmdText = "select fid,name from dnt_forums";
        
using (SqlDataReader reader = SqlHelper.ExecuteReader(CommandType.Text, cmdText, null))
        
{
            
while (reader.Read())
            
{
                Response.Write(reader[
"fid"]);
                Response.Write(reader[
"name"]);
            }

        }

    }

}

安fid取出name版块名

using System;
using System.Web;
using System.Data;
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

        
string cmdText = "select name from dnt_forums where fid=@fid";
        
        SqlParameter parm 
= new SqlParameter("@fid", SqlDbType.Int, 4);//设置Sql参数fid
        parm.Value = fid;

        
object name = SqlHelper.ExecuteScalar(CommandType.Text, cmdText, parm);
        Response.Write(name);
    }

}

嗯,感觉不错,我们的代码质量得到了很大的提高了.
但我们的SqlHelper还少了个很常用的功能ExecuteNonQuery

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

namespace Discuz.Data
{
    
public class SqlHelper
    
{
        
private static string m_connString = "Data Source=(local);" +
            
"User ID=sa;Password=password;Initial Catalog=discuz;Pooling=true"//Sql数据库连接字符串

        
/// <summary>
        
/// 执行一个SqlCommand返回受该SqlCommand影响的行数
        
/// </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);
        
/// </remarks>
        
/// <param name="cmdType">SqlCommand类型 如存储过程或Sql文本命令</param>
        
/// <param name="cmdText">SqlCommand文本 如存储过程名称或Sql语句</param>
        
/// <param name="cmdParms">SqlCommand的参数SqlParameters</param>
        
/// <returns>返回受SqlCommand影响的行数</returns>

        public static int ExecuteNonQuery(CommandType cmdType, string cmdText, params SqlParameter[] cmdParms)
        
{

            SqlCommand cmd 
= new SqlCommand();

            
using (SqlConnection conn = new SqlConnection(m_connString))
            
{
                PrepareCommand(cmd, conn, cmdType, cmdText, cmdParms);
                
int val = cmd.ExecuteNonQuery();
                cmd.Parameters.Clear();
                
return val;
            }

        }


        其它同前面,略

    }

}

4) 小菜第四招    细细体会.
以上SqlHelper类,为微软PetShop4.0中的代码,写的比较好.

这时小菜被老娘拍醒了,,还睡呢,,太阳晒屁股了,,,快起来.....
休息,休息,,,,下篇继续努力.