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;
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;Password=123@abc;"; 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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM