乱用三层的项目
现在三层太流行了,我想至少50%稍微有些规模的项目都是采用三层.本人也开发了几个三层方面的项目,总算见识了不理解三层就开发三层带来的恶果.
什么是稍具规模的项目,以我开发的项目为例吧,(借此把开发过的项目宣传一下,sorry)
如www.ungou.com/ http://www.doocn.com/ 还有一个网站因为公司要求保密,暂时保密,在此向这个公司道歉,确实考虑不周
说真的,我无意宣传,也没有这个脸,因为这三个项目都算是三层方面的失败者(因为我不是项目中的老大),带来的后果我不说大家也知道:混乱,维护难.
三层新观点
数据访问层
1. 数据访问层不应该有事务,应该只是很纯的增,删,改,查询,是否存在等等比较通用的数据访问方法.
2. 业务需求的变化不应该造成该层方法的修改(除非是增加字段等等和表有关联的需求) ,判断是否合格的数据访问层可以 使用这条规则.
3. 数据访问层中的方法对于业务层来说是一个个很纯的小零件(或者说积木吧),举例说吧,一个添加用户的方法中,就只是添加用户,不会再向日志表插入操作日志.这样做的好处是方法职责清晰,稳定(能适应需求变化,一经代码生成后,很少再做修改,就叫做稳定)
4.数据访问层的方法中并不是说就完全没有事务参与,经常是要先判断当前线程上下文是否有事务存在,有事务存在就按事务执行,没有事务就自己创建连接对象.
业务层
1.最主要的任务是 按业务需求 组织调用数据访问层中的方法,就好像搭积木一样,搭积木可以有很多花样,正如业务需求也是一样花样很多. 具体举例说:在业务层中添加用户方法中可能是这样:
开始事务
插入用户
插入操作日志
提交事务
2. 大部分业务需求的变化应只要修改业务层中的代码即可,比如上面添加用户的需求变化了,要求添加用户时能分配角色,上述方法就变成是这样:
开始事务
插入用户
插入用户角色表(用户id)
插入操作日志
提交事务
表现层
暂不谈
基于这种设计的数据层和业务层优点
1. 由于数据访问层的方法就像积木一样,粒度很低,很通用,可以代码生成,业务变化基本上也不会修改改层代码,很稳定很好.
2.业务层代码很灵活,都是一些核心的业务代码.代码量比数据访问层多很多. 经常听到或看到很多人都说"三层代码中的业务层就好像一个转发器一样,没什么代码,把数据访问层的方法转一下就完成任务了,好像是多余的一个层",这种说法在j2ee项目中
一样经常存在,虽然j2ee项目一向被世人认为大部分是比较规范化的,(net项目经常被人说代码很乱,很有地方特色,每个人有每个人的写法,哈哈).
为什么很多人觉得似乎业务层好像没有做什么重要的事,那是因为开发的人把业务搞到数据访问层了,把数据访问方法搞大了,把事务控制搞到数据访问层了.
那有什么缺点呢:
1.事务放在业务层可能造成业务层不能适应多数据库支持特性,因为有些数据库是不支持事务或事务保存点,但是这种机会是很小的,因为主流的适合稍具规模项目的数据库一般都支持
2. 前面说了,数据访问层中的方法一般还要判断当前线程上下文中或方法参数中(比较土的方式)是否有事务存在,然后再进行数据访问操作,关键是:事务在业务层控制,事务还要在数据访问方法中被判断,有点难. 当然,目前有比较好的方式,可以用一些类似spring.net框架来管理事务,以便层和层耦合性低点.
批批现有流行框架
pershop4: 完全没有体现事务在业务层的价值.
discuzNT: 也完全不知道事务在业务层的好处,代码生成工具基本上派不上用场,除了生成实体类,业务变化经常业务和数据两层都要改动.其实数据层是应该比较稳定的.
看看里面的数据访问方法也是先搞大再说,其实这些可以放到业务层
代码摘抄:
Code
public void MovingForumsPos(string currentfid, string targetfid, bool isaschildnode, string extname)
{
SqlConnection conn = new SqlConnection(DbHelper.ConnectionString);
conn.Open();
using (SqlTransaction trans = conn.BeginTransaction())
{
try
{
//取得当前论坛版块的信息
DataRow dr = DbHelper.ExecuteDataset(trans, CommandType.Text, "SELECT TOP 1 * FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [fid]=" + currentfid).Tables[0].Rows[0];
//取得目标论坛版块的信息
DataRow targetdr = DbHelper.ExecuteDataset(trans, CommandType.Text, "SELECT TOP 1 * FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [fid]=" + targetfid).Tables[0].Rows[0];
//当前论坛版块带子版块时
if (DbHelper.ExecuteDataset(CommandType.Text, "SELECT TOP 1 FID FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [parentid]=" + currentfid).Tables[0].Rows.Count > 0)
{
#region
string sqlstring = "";
if (isaschildnode) //作为论坛子版块插入
{
//让位于当前论坛版块(分类)显示顺序之后的论坛版块全部加1(为新加入的论坛版块让位结果)
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [displayorder]=[displayorder]+1 WHERE [displayorder]>={0}",
Convert.ToString(Convert.ToInt32(targetdr["displayorder"].ToString()) + 1));
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
//更新当前论坛版块的相关信息
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [parentid]='{1}',[displayorder]='{2}' WHERE [fid]={0}", currentfid, targetdr["fid"].ToString(), Convert.ToString(Convert.ToInt32(targetdr["displayorder"].ToString().Trim()) + 1));
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
}
else //作为同级论坛版块,在目标论坛版块之前插入
{
//让位于包括当前论坛版块显示顺序之后的论坛版块全部加1(为新加入的论坛版块让位结果)
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [displayorder]=[displayorder]+1 WHERE [displayorder]>={0} OR [fid]={1}",
Convert.ToString(Convert.ToInt32(targetdr["displayorder"].ToString())),
targetdr["fid"].ToString());
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
//更新当前论坛版块的相关信息
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [parentid]='{1}',[displayorder]='{2}' WHERE [fid]={0}", currentfid, targetdr["parentid"].ToString(), Convert.ToString(Convert.ToInt32(targetdr["displayorder"].ToString().Trim())));
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
}
//更新由于上述操作所影响的版块数和帖子数
if ((dr["topics"].ToString() != "0") && (Convert.ToInt32(dr["topics"].ToString()) > 0) && (dr["posts"].ToString() != "0") && (Convert.ToInt32(dr["posts"].ToString()) > 0))
{
if (dr["parentidlist"].ToString().Trim() != "")
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [topics]=[topics]-" + dr["topics"].ToString() + ",[posts]=[posts]-" + dr["posts"].ToString() + " WHERE [fid] IN(" + dr["parentidlist"].ToString().Trim() + ")");
}
if (targetdr["parentidlist"].ToString().Trim() != "")
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [topics]=[topics]+" + dr["topics"].ToString() + ",[posts]=[posts]+" + dr["posts"].ToString() + " WHERE [fid] IN(" + targetdr["parentidlist"].ToString().Trim() + ")");
}
}
#endregion
}
else //当前论坛版块不带子版
{
#region
//设置旧的父一级的子论坛数
DbHelper.ExecuteDataset(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [subforumcount]=[subforumcount]-1 WHERE [fid]=" + dr["parentid"].ToString());
//让位于当前节点显示顺序之后的节点全部减1 [起到删除节点的效果]
if (isaschildnode) //作为子论坛版块插入
{
//更新相应的被影响的版块数和帖子数
if ((dr["topics"].ToString() != "0") && (Convert.ToInt32(dr["topics"].ToString()) > 0) && (dr["posts"].ToString() != "0") && (Convert.ToInt32(dr["posts"].ToString()) > 0))
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [topics]=[topics]-" + dr["topics"].ToString() + ",[posts]=[posts]-" + dr["posts"].ToString() + " WHERE [fid] IN(" + dr["parentidlist"].ToString() + ")");
if (targetdr["parentidlist"].ToString().Trim() != "")
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [topics]=[topics]+" + dr["topics"].ToString() + ",[posts]=[posts]+" + dr["posts"].ToString() + " WHERE [fid] IN(" + targetdr["parentidlist"].ToString() + "," + targetfid + ")");
}
}
//让位于当前论坛版块显示顺序之后的论坛版块全部加1(为新加入的论坛版块让位结果)
string sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [displayorder]=[displayorder]+1 WHERE [displayorder]>={0}",
Convert.ToString(Convert.ToInt32(targetdr["displayorder"].ToString()) + 1));
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
//设置新的父一级的子论坛数
DbHelper.ExecuteDataset(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [subforumcount]=[subforumcount]+1 WHERE [fid]=" + targetfid);
string parentidlist = null;
if (targetdr["parentidlist"].ToString().Trim() == "0")
{
parentidlist = targetfid;
}
else
{
parentidlist = targetdr["parentidlist"].ToString().Trim() + "," + targetfid;
}
//更新当前论坛版块的相关信息
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [parentid]='{1}',[layer]='{2}',[pathlist]='{3}', [parentidlist]='{4}',[displayorder]='{5}' WHERE [fid]={0}",
currentfid,
targetdr["fid"].ToString(),
Convert.ToString(Convert.ToInt32(targetdr["layer"].ToString()) + 1),
targetdr["pathlist"].ToString().Trim() + "<a href=\"showforum-" + currentfid + extname + "\">" + dr["name"].ToString().Trim().Replace("'","''") + "</a>",
parentidlist,
Convert.ToString(Convert.ToInt32(targetdr["displayorder"].ToString().Trim()) + 1)
);
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
}
else //作为同级论坛版块,在目标论坛版块之前插入
{
//更新相应的被影响的版块数和帖子数
if ((dr["topics"].ToString() != "0") && (Convert.ToInt32(dr["topics"].ToString()) > 0) && (dr["posts"].ToString() != "0") && (Convert.ToInt32(dr["posts"].ToString()) > 0))
{
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE " + BaseConfigs.GetTablePrefix + "forums SET [topics]=[topics]-" + dr["topics"].ToString() + ",[posts]=[posts]-" + dr["posts"].ToString() + " WHERE [fid] IN(" + dr["parentidlist"].ToString() + ")");
DbHelper.ExecuteNonQuery(trans, CommandType.Text, "UPDATE " + BaseConfigs.GetTablePrefix + "forums SET [topics]=[topics]+" + dr["topics"].ToString() + ",[posts]=[posts]+" + dr["posts"].ToString() + " WHERE [fid] IN(" + targetdr["parentidlist"].ToString() + ")");
}
//让位于包括当前论坛版块显示顺序之后的论坛版块全部加1(为新加入的论坛版块让位结果)
string sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [displayorder]=[displayorder]+1 WHERE [displayorder]>={0} OR [fid]={1}",
Convert.ToString(Convert.ToInt32(targetdr["displayorder"].ToString()) + 1),
targetdr["fid"].ToString());
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
//设置新的父一级的子论坛数
DbHelper.ExecuteDataset(trans, CommandType.Text, "UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [subforumcount]=[subforumcount]+1 WHERE [fid]=" + targetdr["parentid"].ToString());
string parentpathlist = "";
DataTable dt = DbHelper.ExecuteDataset(trans, CommandType.Text, "SELECT TOP 1 [pathlist] FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [fid]=" + targetdr["parentid"].ToString()).Tables[0];
if (dt.Rows.Count > 0)
{
parentpathlist = DbHelper.ExecuteDataset(trans, CommandType.Text, "SELECT TOP 1 [pathlist] FROM [" + BaseConfigs.GetTablePrefix + "forums] WHERE [fid]=" + targetdr["parentid"].ToString()).Tables[0].Rows[0][0].ToString().Trim();
}
//更新当前论坛版块的相关信息
sqlstring = string.Format("UPDATE [" + BaseConfigs.GetTablePrefix + "forums] SET [parentid]='{1}',[layer]='{2}',[pathlist]='{3}', [parentidlist]='{4}',[displayorder]='{5}' WHERE [fid]={0}",
currentfid,
targetdr["parentid"].ToString(),
Convert.ToInt32(targetdr["layer"].ToString()),
parentpathlist + "<a href=\"showforum-" + currentfid + extname + "\">" + dr["name"].ToString().Trim() + "</a>",
targetdr["parentidlist"].ToString().Trim(),
Convert.ToString(Convert.ToInt32(targetdr["displayorder"].ToString().Trim()))
);
DbHelper.ExecuteDataset(trans, CommandType.Text, sqlstring);
}
#endregion
}
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
throw ex;
}
conn.Close();
}
}
3. 动软代码生成工具生成的三层工厂代码
也是没有考虑把事务放在业务层,生成的数据访问方法经常是要再重写,业务层方法倒是变得相对稳定,作用变小了,其实相对稳定的是数据访问层,不稳定的应该是业务层.
其实造成目前现有流行框架也会发生这种错误是没有良好的业务层事务控制机制,或者说比较优美的实现,当然使用一些类似spring.net框架的除外
本人知识还是比较薄弱的,欢迎释放你的见解,哈哈
看到大家对例子呼声很高,我这边有个事务在业务层的企业库例子
EnterpriseLibrary3.1做的角色,功能管理模块.rar