SQL Server 2008 表数据改变后发送消息(.net 扩展函数法【稳定】【简洁】)

       一直以来应用程序中数据的 PUSH/PULL 应用效果,是作为实时系统的关键点。市面上面各种ESB,要么价格不菲,要么相当庞大,入门都够很长时间。有人说还有说消息服务器MSMQ,ActiveMQ(这个比较推荐,客户端语言支持较为丰富),JSM 等还有WebSocket 等等技术。

       这些消息组件我都尝试用过,可能技术有限,总感觉不满意,上面说的三个消息服务器,MSMQ 微软的,优缺点大家都知道。ActiveMQ 是跨语言(90%的主流语言均有专门的类库支持)跨平台(Java 源程序) 我用作在一个项目上,客户端有Delphi,有C#(这里又有Web 的 ,WinForm 的程序),用下来消息传递很通畅。不过问题在于 ActiveMQ 一旦跨语言,存在一些复杂数据消息传递会丢失。只能使用String 之类简单类型,不过仅作为消息驱动,我觉得问题不大。

       但是还有些实际情况是小项目上面,差不多5W上下的项目上面,客户硬件投入不是太好,有些客户机器上面也存在很多乱七八糟的已建的系统,自己东西越多越容易出问题。 我就想直接通过SQL Server 2008 R2 来进行消息中转,因为在数据库内部,存在有触发器,触发器也就是事件驱动。但问题又来了,如前台如何接收到消息? C# 里面有文件监视对象 FileSystemWatcher,可以对某个文件夹下进行监视,文件夹下面文件变动都可以在C# 程序中得到相应消息。

基本上思路清晰了。

1、SQL Server 中写触发器,一旦监视表发生变动。立即写文件输出

2、C# 编写前端监视器,监视文件变化,一旦文件变化。进行预定逻辑处理。

 

SQL Server 输出文件,我试过了网上很多,有用脚本对象创建文件。有输出XML的,自我感觉要么不稳定,要么不好维护。于是就用C# 来写扩展SQL的函数。

主要函数如下:

 

[SqlFunction(IsDeterministic = true, DataAccess = DataAccessKind.Read)]
        public static String WriteStringToFile(String FileFullPath, String Contend)
        {
            try
            {
                FileInfo Fi = new FileInfo(FileFullPath);
                if (!Fi.Directory.Exists)
                {
                    Fi.Directory.Create();
                }

                using (StreamWriter rw = File.CreateText(FileFullPath))
                {
                    
                    rw.WriteLine(Contend);
                    rw.Flush();
                    rw.Close();
                }
                return "";
            }
            catch(Exception e)
            {
                return e.Message;
            }
        }


在数据库中执行:

开启数据库CLR 支持

--exec sp_configure 'clr enabled', 1;

--开始数据的验证
alter database dbname set TRUSTWORTHY on

 

--创建数据库的外链程序集

use dbname;
create assembly SQLExtLib FROM 'F:\Pro\Demo\SQLExtLib\SQLExtLib\bin\Debug\SQLExtLib.dll'

WITH PERMISSION_SET = UNSAFE

--关于外链程序集参阅

http://msdn.microsoft.com/zh-cn/library/ms189524.aspx

 

--此过程搞得很头疼,因为涉及SQL Server 安全,我们编写的类库中方法都要写一些权限的标识,否则挂载不到数据库中。

--而且程序集要进行强名称签名,才能挂接上去。

--至于为什么要这么做?或者其他一些特殊的用法,请参考 Pro.SQL.Server.2005.Assemblies.Dec.2005 这本电子书(网上搜一下,一堆!),里面内容很详细。大体看个条例也就差不多了。差不多我就一早上看看大概,看完加一下午写写DEMO,就出来。详细都没咋个看(现在搞技术,搞得很浮躁啊!)

 

--建立SQL中函数与程序集函数对应(以便以后直接使用 Select WriteStringToFile('d:\demo\1.txt','Hello World!') 直接调用)


create function WriteStringToFile(@FileFullName as nvarchar(max),  @FileContend AS  nvarchar(max))

returns nvarchar(max)

with returns null on null input

external name [SQLExtLib].[SQLExtLib.Function.StringHelper.StringFunc].[WriteStringToFile]

GO

 

OK~ 挂载好数据库外链程序集以后,找个表来测试一下。创建触发器

CREATE TRIGGER [dbo].[UserTableChangedEvent] ON [dbo].[T_SYS_UserInfo]
    FOR INSERT, DELETE, UPDATE
    
AS
    BEGIN
	DECLARE @Contend AS VARCHAR(100)
	DECLARE @FileName AS VARCHAR(MAX)
		SET @Contend = CONVERT(VARCHAR(100), GETDATE(), 20) ;
		SET @FileName ='D:\\MSG\\'+CONVERT(varchar(12) , getdate(), 112 )+'\\'+ convert(nvarchar(50), NEWID())+'.TXT'
       
        --SELECT  * ,
        --        'old'
        --FROM    DELETED
      
        --SELECT  * ,
        --        'new'
        --FROM    INSERTED 
        
       SET @Contend = CONVERT(VARCHAR(100), GETDATE(), 20);
       Select dbo.WriteStringToFile(@FileName, @Contend)
                

    END
    

GO


这个就是我写的测试触发器,仅是为了记录一个消息在特定的文件夹位置,当然你要具体咋个触发,添加触发、修改触发,等等情况,就丰富则个触发器即可,我这里不存在任何业务在里面。

--这里我试验下来,确实写文件使用 OLE 对象来写的话,问题多多,相当不稳定。而且有个很扯淡的事情,如果你的SQL 是64位,调用32位的OLE 对象,呵呵,搞死人的。 这个是血的教训,原来在开发环境中我们调用JavaScript 对象的运算函数,因为其可以混合运算,而且里面还是写脚本来执行。我们就想数据库里面存脚本函数之类,运行时执行,这种相对也灵活。想法很不错,开发环境一样都没问题,但是投入用户环境就不行了,客户数据库这个是64位SQL,我们的数据中调OLE对象直接报错,而且参考文献都找不到。  网上那些大量调用OLE 的SQL 存过这些,大家一定要有个把握。 因为这,也就推荐自己写CLR DLL 要好些了。

闲话扯了些,继续~

 

--上面看到,每触发一次写一个文件,有人说为什么输出不在一个文件里面? 是不是写一个文件里面,前端无法监听? 情况是这种,无论写一个文件,或者是两个文件,前台都可以获取到消息,主要分开写是为了规避写和读文件的占用问题.。当然也可以控制,需要 写文件函数,还有监视文件 函数两个 对应匹配,想想都是复杂。但是 对我这种懒汉来说,少写一句话,我就绝对不会多去写。到此数据库这边基本完成了,可以手工去改两个数据,看看写了文件没有。

还有一点需注意,有些时候输出文件夹权限要设定给服务有一定的读写权限。比如上面D:\MSG 文件夹 给NetWork Service 读写的权限吧。

 

--最后写前台的监视

我用的是 文件改变事件  FSW.Changed += new FileSystemEventHandler(FSW_Changed);

也可以使用 Created 事件,等等。。。

public partial class Form1 : Form
    {
        FileSystemWatcher FSW;

        public Form1()
        {
            InitializeComponent();
            FSW = new FileSystemWatcher(@"d:\MSG\");
            FSW.EnableRaisingEvents = true;
            FSW.Changed += new FileSystemEventHandler(FSW_Changed);
        }
        Int32 Count = 0;

        void FSW_Changed(object sender, FileSystemEventArgs e)
        {
            if (e.ChangeType == WatcherChangeTypes.Changed)
            {
                BackgroundWorker NewThread = new BackgroundWorker();
                NewThread.DoWork += new DoWorkEventHandler(NewThread_DoWork);
                NewThread.RunWorkerAsync(Count);
                Count++;
            }
          
           
        }
        String TempString = @"
ANONYMOUS:[000000]

BABYLON-5.DIR;1          4  9-MAR-2002 23:35 [CTRL_C,ANOMALY] (RWE,RWED,RE,RE)
CHECK_LICENSES.COM;44
                         8  7-OCT-2003 13:39 [CTRL_C,ANOMALY] (RWED,RWED,RE,RE)
DECWINDOWS.DIR;1        22  9-MAR-2002 23:49 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
DOC.DIR;1                1 10-MAR-2002 00:06 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
FLIGHT-SIM.DIR;1         1 10-MAR-2002 00:19 [CTRL_C,ANOMALY] (RWE,RWED,RE,RE)
FREE_NETPROGRAMS.DIR;1
                         1 10-MAR-2002 00:19 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
FTP_TEMP.DIR;1           1  7-NOV-2003 22:42 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
GNU-VMS.DIR;1            1 10-MAR-2002 00:23 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
GRAPHICS.DIR;1           1 10-MAR-2002 00:37 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
IMAGES.DIR;1             2 10-MAR-2002 00:37 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
MACINTOSH.DIR;1          1 10-MAR-2002 02:10 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
MAILLISTS.DIR;1          1 10-MAR-2002 02:15 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
MSDOS.DIR;1              1 10-MAR-2002 02:15 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
MUSIC.DIR;1              1 10-MAR-2002 02:16 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE)
PROGRAMMING.DIR;1        1 10-MAR-2002 02:19 [CTRL_C,ANOMALY] (RWE,RWE,RE,RE) ....省略测试数据(约1.3M左右数据,用于写接收到消息后,前端写数据内容)"
  void NewThread_DoWork(object sender, DoWorkEventArgs e)
        {
            Int32 A = 0;
            for (int i = 0; i < 100000; i++)
            {
                A++;
                
            }
            StreamWriter rw = File.CreateText(@"D:\LOG\" + e.Argument.ToString()+".txt");

            rw.WriteLine(TempString);
            rw.Flush();
            rw.Close();   
        }


--接收端也是写成多线程,不容易被高频度消息堵死,也是规避消息并发处理上的问题,保证一个消息来,我就派个线程去做这个消息的事情。否则一个线程里面搞,也做得出来,只是太杀脑细胞了。


  void NewThread_DoWork(object sender, DoWorkEventArgs e)
        {
            Int32 A = 0;
            for (int i = 0; i < 100000; i++)
            {
                A++;
                
            }
            StreamWriter rw = File.CreateText(@"D:\LOG\" + e.Argument.ToString()+".txt");

            rw.WriteLine(TempString);
            rw.Flush();
            rw.Close();   
        }


我这边作了个测试,在SQL 里面写循环写 3000个输出,接收方同样能接收到3000个日志。比起OLE 对象创建文件稳定不少!

declare @i int 
set @i=1 
while @i<3001 
begin 
DECLARE @Contend AS VARCHAR(100)
	DECLARE @FileName AS VARCHAR(MAX)
	SET @Contend = CONVERT(VARCHAR(100), GETDATE(), 20) ;
	SET @FileName ='D:\\MSG\\'+CONVERT(varchar(12) , getdate(), 112 )+'\\'+ convert(nvarchar(50), NEWID())+'.TXT'
       SET @Contend = CONVERT(VARCHAR(100), GETDATE(), 20);
       Select dbo.WriteStringToFile(@FileName, @Contend)
set @i=@i+1 
end


最终结果图(左边是SQL 写文件,右边前台接收到又写的日志)



 

这种小模式的解决方案,可能会对一些特殊情况的处理会是个思路。

我个人比较喜欢这种模式,简单,容易维护。

不喜勿喷,欢迎切磋交流。

源代码不上传,全部都在文章里面。

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值