Sql触发器调用外部程序实现数据同步

首先创建两个数据库:SyncA是数据源,SyncB是对SyncA进行同步的数据库。

在SyncA和SyncB中分别创建Source表和Target表,实际业务中,两张表的结构大多不相同。

   

然后创建一个类库的项目:MySync(注意项目的版本,Sql08不支持的.net 4.0及更高版本)

下面是同步程序代码:

using System;
using System.Data;
using System.Data.Sql;
using Microsoft.SqlServer.Server;
using System.Data.SqlClient;
using System.Data.SqlTypes;

namespace MySync
{
    public class SyncDataBase
    {       [SqlFunction(SystemDataAccess = SystemDataAccessKind.Read, DataAccess = DataAccessKind.Read)]
        public static string Sync(string strSql)
        {
            string result = "true";

            string strConn = @"Data Source=localhost;Initial Catalog=SyncB;User ID=sa;[email protected];";
            try
            {
                using (SqlConnection connection = new SqlConnection(strConn))
                {
                    connection.Open();
                    SqlCommand command = new SqlCommand(strSql, connection);
                    command.CommandType = CommandType.Text;
                    command.ExecuteNonQuery();
                    connection.Close();
                }
            }
            catch (Exception ex)
            {
                result = "false:" + ex.ToString();
            }

            return result;
        }
    }
}

接下来要对类库项目进行签名,签名后编译【项目】:

启用CLR功能:默认情况下,Sql Server中的CLR是关闭的,所以我们要执行如下命令打开SyncA数据库的CLR。

exec sp_configure ‘clr enabled‘,1
reconfigure
go

注册DLL:

为了调用我们写的那个方法,需要在SQL Server中注册我们刚刚编译好的那个DLL。在此之前,要知道在这个项目中如果要访问服务器之外的资源是要配置权限的。如果不配置,后面操作中会出现类似下面的错误。我找到的关于授权配置的内容:连接

创建登录名和密钥,如果程序集有变更,要删除密钥和登录名重新创建:

USE master;
GO  

CREATE ASYMMETRIC KEY SQLCLRSyncKey FROM EXECUTABLE FILE = ‘C:\MySync.dll‘
CREATE LOGIN SQLCLRSyncLogin FROM ASYMMETRIC KEY SQLCLRSyncKey
GRANT EXTERNAL ACCESS ASSEMBLY TO SQLCLRSyncLogin;
GO 
DROP LOGIN SQLCLRSyncLogin
DROP ASYMMETRIC KEY SQLCLRSyncKey

创建程序集,DLL变更后要删除重新创建:

USE SyncA;
GO  

create ASSEMBLY MySync
FROM ‘C:\MySync.dll‘
WITH PERMISSION_SET = EXTERNAL_ACCESS;
GO 

然后创建一个函数用于调用这个DLL:

CREATE FUNCTION dbo.fun_sync
(
    @strSql nvarchar(max)
)
RETURNS nvarchar(max)
AS EXTERNAL NAME [MySync].[MySync.SyncDataBase].[Sync] 

先来测试一下,在SyncA中执行查询:

SELECT dbo.fun_sync(‘insert into Target(Id,Name,SyncTime) values (null,null,getdate())‘)

SyncB中添加了一条数据:

下面使用触发器自动的从SyncA中将数据同步到SyncB中,其中的tt表是我临时创建的,用于保存触发器调用返回的结果:

create Trigger tr_source
on [Source]
for INSERT

AS
begin
declare @strSql nvarchar(max)
select @strSql=‘insert into Target(Id,Name,SyncTime) values (‘‘‘+cast(Id as nvarchar)+‘‘‘,‘‘‘+Title+‘‘‘,getdate())‘ from inserted

--执行
declare @result nvarchar(max)
select @result=dbo.fun_sync(@strSql)

insert into tt(tt) values (@result)
end

直接执行函数没有问题,但是触发器去调用函数执行却出现异常:

false:System.Data.SqlClient.SqlException: 其他会话正在使用事务的上下文。
在 System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
在 System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
在 System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
在 System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
在 System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
在 System.Data.SqlClient.SqlInternalConnectionTds.PropagateTransactionCookie(Byte[] cookie)
在 System.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
在 System.Data.SqlClient.SqlInternalConnection.Enlist(Transaction tx)
在 System.Data.SqlClient.SqlInternalConnectionTds.Activate(Transaction transaction)
在 System.Data.ProviderBase.DbConnectionInternal.ActivateConnection(Transaction transaction)
在 System.Data.ProviderBase.DbConnectionPool.GetConnection(DbConnection owningObject)
在 System.Data.ProviderBase.DbConnectionFactory.GetConnection(DbConnection owningConnection)
在 System.Data.ProviderBase.DbConnectionClosed.OpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory)
在 System.Data.SqlClient.SqlConnection.Open()
在 MySync.SyncDataBase.Sync(String strSql)

这个错误中包含了一个false值,说明触发器调用时已经可以走到DLL这一步了。考虑到在查询中直接执行函数,走到DLL这一步是没有错误的。那么错误就发生在触发器和DLL调用产生的冲突,冲突在访问数据库上面,再深入的原因,我也没有找到。

下面使用另外一种方式实现同步,因为错误是触发器和DLL的数据库访问冲突,那么我就绕过数据库的访问。将触发器产生的SQL脚本保存到某个目录下面,然后通过其他程序监听这个目录,执行脚本文件,实现同步。

类库代码

using System;
using System.Data;
using System.Data.Sql;
using Microsoft.SqlServer.Server;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.IO;

namespace MySync
{
    public class SyncDataBase
    {
        [SqlFunction(SystemDataAccess = SystemDataAccessKind.Read, DataAccess = DataAccessKind.Read)]
        public static string Sync(string strSql)
        {
            string result = "true";

            try
            {
                if (!Directory.Exists("c:\\SyncLog"))
                {
                    Directory.CreateDirectory("c:\\SyncLog");
                }
                string fileName = @"c:\\SyncLog\\" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt";
                if (File.Exists(fileName))
                    File.Delete(fileName);

                using (StreamWriter sw = File.CreateText(fileName))
                {
                    sw.WriteLine(strSql);
                }
            }
            catch (Exception ex)
            {
                result = "false:" + ex.ToString();
            }

            return result;
        }
    }
}

另外创建一个监听程序:MyListen

using System;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Configuration;
using System.Threading;
using System.IO;

namespace MyListen
{
    class Program
    {
        static void Main(string[] args)
        {
            string connSync = ConfigurationManager.ConnectionStrings["connSync"].ToString();
            string filePath = ConfigurationManager.AppSettings["filePath"];
            while (true)
            {
                //所有txt文件
                string[] fileList = DirFile.GetFileNames(filePath, "*.txt", true);
                foreach (var f in fileList)
                {
                    string strSql = "";
                    using (StreamReader sr = new StreamReader(f))
                    {
                        string line;
                        while ((line = sr.ReadLine()) != null)
                        {
                            strSql += line + " ";
                        }
                        sr.Close();
                    }
                    try
                    {
                        using (SqlConnection connection = new SqlConnection(connSync))
                        {
                            connection.Open();
                            SqlCommand command = new SqlCommand(strSql, connection);
                            command.CommandType = CommandType.Text;
                            command.ExecuteNonQuery();
                            connection.Close();
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.ToString());
                    }
                    File.Delete(f);
                }
                //每10秒扫描一次
                Thread.Sleep(5 * 1000);
            }
        }
    }
}

只要将监听程序打开,就可以实现对数据的同步。项目和数据库下载

参考:

http://msdn.microsoft.com/zh-cn/library/Microsoft.SqlServer.Server.SqlFunctionAttribute_properties(v=vs.100).aspx

http://blog.sina.com.cn/s/blog_59c41d0d0100esjn.html

http://www.cnblogs.com/wshcn/archive/2011/12/02/2271630.html

http://www.cnblogs.com/edong/archive/2010/03/10/1682172.html

http://www.cnblogs.com/hsrzyn/archive/2013/05/28/1976555.html

时间: 2024-10-18 04:06:46

Sql触发器调用外部程序实现数据同步的相关文章

SqlServer调用外部程序实现数据同步

首先创建两个数据库:SyncA是数据源,SyncB是对SyncA进行同步的数据库. 在SyncA和SyncB中分别创建Source表和Target表,实际业务中,两张表的结构大多不相同.     然后创建一个类库的项目:MySync(注意项目的版本,Sql08不支持的.net 4.0及更高版本) 下面是同步程序代码: using System; using System.Data; using System.Data.Sql; using Microsoft.SqlServer.Server;

Sql触发器调用外部程序对两个数据库进行数据同步

首先创建两个数据库:SyncA是数据源,SyncB是对SyncA进行同步的数据库. 在SyncA和SyncB中分别创建Source表和Target表,实际业务中,两张表的结构大多不相同.     然后创建一个空的项目:ClassLibrarySync

SQL 2008提供几种数据同步方式

SQL 2008提供几种数据同步的方式如下. 1.日志传送(Log Shipping),定时将主数据库的日志备份,恢复到目标数据库. 2.数据库镜像(Database Mirror),原理同日志传送, 另有提供自动接管功能. 3.建立复制(Replication),SQL 2000既有功能.

使用C#实现sql server 2005 和Oracle 数据同步

1.背景: 公司的一个项目进行服务扩展,添加了短信服务平台,实现平台按照预定义的规则给用户主动发送短信和用户点播,两种方式.短信平台需能够接入三网(移动.联通.电信).目前只接入了移动MAS机.用户点播时发送的短信通过MAS机,发送的内容最后保存在sql serer 2005 数据库的表T 中,然后需要根据用户发送的点播代码给用户返回对应的信息. 用户的信息都在业务系统中,业务系统是Oracle 的数据库.两者都处于同一个局域网之内.由于短信平台是一个产品我们无法进行操作,和项目经理商议后决定将

SQL触发器调用.NET的类方法续SQLCLR应用

SQL CLR (SQL Common Language Runtime) 是自 SQL Server 2005 才出现的新功能,它将.NET Framework中的CLR服务注入到 SQL Server 中,使得.NET代码可在SQL Server服务器进程中执行. 通过在 Microsoft SQL Server 中托管 CLR(称为 CLR 集成),开发人员可以在托管代码中编写存储过程.触发器.用户定义函数.用户定义类型和用户定义聚合函数, 改变了以前只能通过T-SQL语言来实现这些功能的

MySQL触发器实现两表数据同步(详解)

1. 创建一个数据表 order_1,同时复制出一份表order_2表结构如下:CREATE TABLE a_order_1 (order_id int(11) NOT NULL AUTO_INCREMENT,order_sn varchar(100) DEFAULT NULL,user_nick varchar(100) DEFAULT NULL,user_mobile varchar(100) DEFAULT NULL,address varchar(255) DEFAULT NULL,st

实现mysql和redis之间的触发数据同步——mysql 触发器+gearman+php.worker

上回一次我们已经实现了 redis 作为 mysql 的缓存服务器,但是如果更新了 mysql,redis 中仍然会有对应的 KEY,数据就不会更新,此时就会出现 mysql 和 redis 数据不一致的情 况. 详情请见        基于redis缓存数据库实现lnmp架构高速访问 所以接下来就要通过 mysql 触发器将改变的数据同步到 redis 中. 因为mysql和redis数据格式不同,不能实现直接同步,所以 将MySQL数据首先放入Gearman中,然后通过一个自己编写的PHP

redis作为mysql的缓存服务器(读写分离,通过mysql触发器实现数据同步)

一.redis简介Redis是一个key-value存储系统.和Memcached类似,为了保证效率,数据都是缓存在内存中.区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步.在部分场合可以对关系数据库起到很好的补充作用.它提供了Java,C/C++(hiredis),C#,PHP,JavaScript,Perl,Object-C,Python,Ruby等客户端,使用很方便. 二.架构图<ignore_js_

SQLServer与Oracle的数据同步(触发器trigger)

说到同步,其实是靠"作业"定时调度存储过程来操作数据,增,删,改,全在里面,结合触发器,游标来实现,关于作业调度,使用了5秒运行一次来实行"秒级作业",这样基本就算比较快的"同步" 做的是SQL Server往Oracle端同步,先在sql server上建立往Oracle端的链接服务器,我用一个视图"封装"了一下链接服务器下的一张表. create view v_ora_PUBLISHLASTREC as select *