一般在N层设计中,数据层绝不应该牵扯到逻辑.理由很多,但最重要的一点,应该是有利于维护.如果面向的数据库是无存储过程的,那么这个法则应该被遵守.比如ACCESS,MYSQL.
如果使用MS SQLServer等其它有储存过程的数据库,我个人认为大不必如此.有时候一个好的存储过程能带来更良好的性能合可读性.
经典的架构就和牛顿的经典力学一样,并不是放之四海而皆为准的,不用毛主席的话说,那就是没有银弹.架构,OOP,这些前辈的经验都是面对一个问题的——客户的需求要变。如果客户的需求不变动,那要OOP,要设计模式,要架构有什么用?面向过程完全能写出更加高效,更加大众化的程序来。
我认为带逻辑的存储过程完全是AOP的实例,虽然存储过程不具备OOP的特征。数据层用来提供数据,至于怎么提供数据我认为没有必要过于关心。虽然一个带逻辑的存储过程可能会进行很多操作,以至于耦合度太高。但可以认为它是一个方法,甚至可以理解为一个封装的组件。
比如,在项目实施中实现站内短信息系统。当然,我这个例子可能不够好。
站内短消息要实现站内短信息的发送,收取,群发,黑名单过滤功能,涉及到批量更新数据及过滤。
建立3个表:
1,Messages
(MessagesID,UserFromID,Subject,[Content],SendToCount,IsKilledBySender)
分别是 ID,用户ID,主题,内容,发送数量,是否被发件人删除 //我在这里做了适当简化
2. MessagesToUsers
(MessageID,UserID,IsReaded)
分贝是ID,用户ID,是否已读
3. BlackList
(ListID,UserOwner,UserTarget)
分别是 ID,屏蔽的用户,被屏蔽的用户
要发送一次群发,如果用ADO.NET处理,需要以下步骤
1. 连接数据库;
2. 查询获得收件人的ID; //用户发送是选或填写用户名列表,需要遍历数据库对应ID
3. 插入消息; //把消息写入Messages
4. 以收件人数开始循环; //把收件人和信息得关系写入MessagesToUsers,是1对多关系
5. 循环中读取黑名单查看该发件人是否被收件人屏蔽;
6. 如果屏蔽则返回循环
7. 如果没屏蔽, 插入消息与收件人关系
这样就实现了.看上去复杂,却是最简单,最容易想到的办法.
而这个算法无疑使效率变得非常低.
而可以使用以下储存过程代替
2 set QUOTED_IDENTIFIER ON
3 GO
4 -- =============================================
5 -- Author: <Author,,Name>
6 -- Create date: <Create Date,,>
7 -- Description: <Description,,>
8 -- =============================================
9 ALTER PROCEDURE [dbo].[SendMessages]
10 (
11 @subject nvarchar(200), --主题
12 @content text, --内容
13 @sendto nvarchar(2000), --收件人列表,以,分割的字符
14 @from int, --发件人
15 @type bit --消息类型(0为用户消息,1为系统提醒)
16 )
17 AS
18 BEGIN
19 SET NOCOUNT ON;
20 --插入消息
21 insert into InMessages (UserFromID,Subject,[Content],SendTime,IsKilledBySender,ReadCount,SendToCount,MessageType)
22 values (@from,@subject,@content,getDate(),0,0,0,@type);
23 --取得插入消息ID
24 declare @messageid int
25 set @messageid = @@IDENTITY
26 --构造虚拟表(在查询用户ID的基础上,添加自定字段)
27 declare @sql nvarchar(400)
28 set @sql = 'select MessageID=' + cast(@@IDENTITY as varchar) + ',UserID,IsReaded=0 from Users where DisplayName in ( ' + @sendto + ') and (select count(UserTarget) from MsgBlackList where UserOwner = UserID and UserTarget = ' + cast(@from as varchar) + ') = 0'
29 insert into InMessagesToUsers EXEC(@sql)
30
31 --更新该邮件发送人数
32 declare @sendcount int
33 set @sendcount = (select count(mu.MessageID) from InMessagesToUsers as mu where mu.MessageID = @messageid)
34 update InMessages set SendToCount = @sendcount where MessageID = @messageid
35
36 --返回邮件发送人数,表字段名:SendCount
37 select SendCount = @sendcount
38 END
39
40
传入参数里有得字段被我省略了(SQL Server 2005).
发送人发送消息被记录到InMessages 表
收件人由字符串组成,所以查询采用select in
收件人于信件的关系被记录在表InMessagesToUsers 表
黑名单表MsgBlackList
而调用也非常简单
2 /// 执行存储过程SendMessages
3 /// </summary>
4 /// <returns></returns>
5 public override string ProcessAction()
6 {
7 string subject = GetQueryValue("Subject");
8 string content = GetQueryValue("Content");
9 string sendto = GetQueryValue("SendTo");
10 sendto = "'" + sendto + "'";
11 sendto = sendto.Replace(",", "','"); //sendto是有,连接的收件人,这里处理了才能被select in 使用
12
13 SUser s = LoginHelper.CrrentUser();
14 int from = s.UserID;
15
16 if (from != 0)
17 {
18 bool MessageType = false;
19 SqlParameter[] parms = {
20 new SqlParameter("@subject",SqlDbType.NVarChar,200),
21 new SqlParameter("@content",SqlDbType.Text),
22 new SqlParameter("@sendto",SqlDbType.NVarChar,4000),
23 new SqlParameter("@from",SqlDbType.Int),
24 new SqlParameter("@type",SqlDbType.Bit)
25 };
26 parms[0].Value = subject;
27 parms[1].Value = content;
28 parms[2].Value = sendto;
29 parms[3].Value = from;
30 parms[4].Value = MessageType;
31
32
33 DataTable dt = DBHelper.ExecuteTable
34 (
35 CommandType.StoredProcedure,
36 "SendMessages",
37 parms
38 );
39
40 //sendcount应为INT类型,这里用string用于返回
41 string sendcount = dt.Rows[0]["SendCount"].ToString();
42
43 return "2|消息共发送给了" + sendcount + "人|SendMessages.aspx";
44
45 }
46 else
47 {
48 return "2|请登陆后再发|Login.aspx";
49 }
50 }
51 }
无论效率和可读性都比常规办法要好很多.
存储过程SendMessages可以理解为一个黑箱子.处理完返回实际发送给了多少人.