Click here to Skip to main content
Click here to Skip to main content

An Alternative to Sync Data from SQL Server to MySQL

By , 16 Aug 2012
 

Introduction

My company has the need to synchronize data on a real time basis from MS SQL Server 2008 to a MySQL database. At first I thought that it could be done with table triggers after setting up a linked server to a MySQL table, and then it would not be a big problem.

So I downloaded and installed the MySQL ODBC Connector for Windows 7. I set up an ODBC data source and then a linked server, but I got this error message:

sync_sql_mysql/error.jpg

I googled and googled and could not fix this issue, even after I re-installed SQL Server (you would be much appreciated if you let me know how to fix it by sending an email to to_scottleo@yahoo.com).

I had to turn to a work-around.

The solution

My idea is to write a trigger to call a CLR Stored Procedure which calls a Web Service. The Web Service will perform the MySQL data manipulation.

Let’s start.

Step 1: Download and install MySQL Connector/.NET for Windows. Create a Class Library in Visual Studio 2008. Let’s name it MySqlDataManipulation.

using System;
using MySql.Data.MySqlClient;

namespace MySqlDataManipulation
{
    public class MySqlData
    {
        private MySqlConnection _conn;
        public MySqlData(string sConnStr)
        {
            _conn = new MySqlConnection(sConnStr);
        }

        public int ExecuteNonQuery(string sSqlStmt)
        {
            _conn.Open();
            MySqlCommand cmd = new MySqlCommand(sSqlStmt, _conn);
            cmd.CommandType = System.Data.CommandType.Text;
            cmd.CommandText = sSqlStmt;
            int returnCode = cmd.ExecuteNonQuery();

            return returnCode;
        }
    }
}

Build it and copy the DLL file to the web bin directory for our Web Service to use it.

Step 2: Create an ASP.NET Web Service.

using System;
using System.Web.Services;
using MySqlDataManipulation;
using System.Web.Configuration;

[WebService]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class WebSyncService : System.Web.Services.WebService {

    public WebSyncService () {
    }

    [WebMethod]
    public void SyncMySQLData(string sSqlStmt)
    {
        String sMySqlConnStr = 
          WebConfigurationManager.ConnectionStrings["SyncMySQL"].ToString();
        MySqlData d = new MySqlData(sMySqlConnStr);
        d.ExecuteNonQuery(sSqlStmt);
        return;
    }
}

Step 3: Use Visual Studio 2008 to build a CLR Stored Procedure assembly.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using MySQLRealTimeSync.com.xxxxx.www;

public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void SaveData(String sSqlStmt)
    {
        WebSyncService sync = new WebSyncService();
        sync.SyncMySQLData(sSqlStmt);
    }
}

Before compiling, set the project build output to generate the serialization assembly to “On”. After compiling, we’ll get two files: MySQLRealTimeSyncStoredProcedure.dll and MySQLRealTimeSyncStoredProcedure.XmlSerializers.dll.

Then in SQL Server Management Studio, register the new assemblies MySQLRealTimeSyncStoredProcedure, MySQLRealTimeSyncStoredProcedure.XmlSerializers against these two DLLs, respectively. Remember to set "Permission set" to "External access". It is also probably needed to run the following codes depending on the database settings.

EXEC sp_configure 'clr enabled', 1
reconfigure;
GO
EXEC ('ALTER DATABASE dbname SET TRUSTWORTHY ON')
GO

Step 4: Write a regular Stored Procedure.

CREATE PROCEDURE MySqlDataSync
     @sSqlStmt nvarchar(255)
AS EXTERNAL NAME MySQLRealTimeSyncStoredProcedure.StoredProcedures.SaveData;
GO

Step 5: Write a table trigger.

USE MyDatabase

IF OBJECT_ID('MySchema.TR_SyncToMySQL', 'TR') IS NOT NULL
    DROP TRIGGER MySchema.TR_SyncToMySQL
GO

SET NOCOUNT ON
GO

CREATE TRIGGER MySchema.TR_SyncToMySQL ON MySchema.MyTable
FOR INSERT, DELETE, UPDATE
AS
BEGIN
    DECLARE
      @InsertCount        integer,
      @DeleteCount        integer,
      @TableName        varchar(255),
      @MySQLTableName        varchar(255),
      @FieldName        nvarchar(255),
      @IdentityColName    nvarchar(255),
      @IdentityColVal    nvarchar(255),
      @OldValue            nvarchar(max),
      @NewValue            nvarchar(max),
      @ColCount         bigint,
      @ColTotal         bigint,
      @SqlString        varchar(max),
      @SqlStringTemp    varchar(max),
      @HasModifiedCol    bit

    SET @MySQLTableName = 'MySQLTableName'
    SET @InsertCount = (SELECT Count(*) FROM INSERTED)
    SET @DeleteCount = (SELECT Count(*) FROM DELETED)
    
    SELECT @TableName = OBJECT_NAME(PARENT_OBJ)
    FROM SYSOBJECTS
    WHERE id = @@PROCID
    
    SELECT @ColTotal = COUNT(COLUMN_NAME)
    FROM   INFORMATION_SCHEMA.COLUMNS
    WHERE  TABLE_NAME = @TableName
    
    SET @ColCount = 0

    SELECT @IdentityColName = name 
    FROM syscolumns
    WHERE OBJECT_NAME(id) = @TableName AND
    COLUMNPROPERTY(id, name, 'IsIdentity') = 1
    
    IF OBJECT_ID('tempdb..#myTemp') IS NOT NULL
    DROP TABLE #myTemp;
    CREATE TABLE #myTemp (FieldValue nvarchar(max));
    
    IF @InsertCount > @DeleteCount -- insert action
    BEGIN
        DECLARE @ValueString nvarchar(max)
        WHILE ((SELECT @ColCount) < @ColTotal)
        BEGIN
          SET @ColCount = 1 + @ColCount
            
          SELECT @FieldName = rtrim(name)
          FROM syscolumns
          WHERE OBJECT_NAME(id) = @TableName AND colid = @ColCount
                        
          IF @FieldName = @IdentityColName CONTINUE
            
          IF OBJECT_ID('tempdb..#myInserted1') IS NOT NULL
          DROP TABLE #myInserted1;
            
          SELECT * INTO #myInserted1 FROM INSERTED;

          DELETE FROM #myTemp;
          SET @SqlStringTemp = 'INSERT INTO #myTemp (FieldValue) SELECT TOP 1 CAST(' + 
                               @FieldName + ' AS nvarchar(max)) FROM #myInserted1';
          EXEC(@SqlStringTemp);
          SET @NewValue = (SELECT FieldValue FROM #myTemp);
          IF @NewValue IS NULL SET @NewValue = ''
            
          IF @SqlString IS NULL
          SET @SqlString = 'INSERT ' + @MySQLTableName+ ' (';
          IF @ValueString IS NULL
          SET @ValueString = ') VALUES ('
            
          SET @SqlString = @SqlString + @FieldName + ', '
          SET @ValueString = @ValueString + '''' + @NewValue + ''', '
        END

        EXEC dbo.MySqlDataSync REPLACE(@SqlString + @ValueString + ')', ', )', ')')
    END
    ELSE IF @InsertCount < @DeleteCount -- delete action
    BEGIN
        WHILE ((SELECT @ColCount) <= @ColTotal)
        BEGIN
          SET @ColCount = 1 + @ColCount
          SELECT @FieldName = rtrim(name)
          FROM syscolumns
          WHERE OBJECT_NAME(id) = @TableName AND colid = @ColCount
            
          IF @FieldName <> @IdentityColName CONTINUE
                        
          IF OBJECT_ID('tempdb..#myDeleted1') IS NOT NULL
          DROP TABLE #myDeleted1;
            
          SELECT * INTO #myDeleted1 FROM DELETED;

          DELETE FROM #myTemp;
          SET @SqlStringTemp = 'INSERT INTO #myTemp (FieldValue) SELECT TOP 1 CAST(' + 
                               @IdentityColName + ' AS nvarchar(max)) FROM #myDeleted1';
          EXEC(@SqlStringTemp);
          SET @IdentityColVal = (SELECT FieldValue FROM #myTemp);
        END
        EXEC dbo.MySqlDataSync 'DELETE FROM ' + @MySQLTableName+ ' WHERE ' + 
             @IdentityColName + ' = ''' + @IdentityColVal + ''''
    END
    ELSE IF @InsertCount = @DeleteCount -- update action
    BEGIN
        SET @HasModifiedCol = 0
        WHILE ((SELECT @ColCount) < @ColTotal)
        BEGIN
          SET @ColCount = 1 + @ColCount
               SELECT @FieldName = rtrim(name)
          FROM syscolumns
          WHERE OBJECT_NAME(id) = @TableName AND colid = @ColCount
            
          IF @FieldName = @IdentityColName CONTINUE
            
          IF OBJECT_ID('tempdb..#myInserted') IS NOT NULL
          DROP TABLE #myInserted;
          SELECT * INTO #myInserted FROM INSERTED;
            
          DELETE FROM #myTemp;
          SET @SqlStringTemp = 'INSERT INTO #myTemp (FieldValue) SELECT TOP 1 CAST(' + 
                               @IdentityColName + ' AS nvarchar(max)) FROM #myInserted';
          EXEC(@SqlStringTemp);
          SET @IdentityColVal = (SELECT FieldValue FROM #myTemp);
         
          DELETE FROM #myTemp;
          SET @SqlStringTemp = 'INSERT INTO #myTemp (FieldValue) SELECT TOP 1 CAST(' + 
                               @FieldName + ' AS nvarchar(max)) FROM #myInserted';
          EXEC(@SqlStringTemp);
          SET @NewValue = (SELECT FieldValue FROM #myTemp);
          IF @NewValue IS NULL SET @NewValue = ''
            
          IF OBJECT_ID('tempdb..#myDeleted') IS NOT NULL
          DROP TABLE #myDeleted;
          SELECT * INTO #myDeleted FROM DELETED;

          DELETE FROM #myTemp;
          SET @SqlStringTemp = 'INSERT INTO #myTemp (FieldValue) SELECT TOP 1 CAST(' + 
                               @FieldName + ' AS nvarchar(max)) FROM #myDeleted';
          EXEC(@SqlStringTemp);
          SET @OldValue = (SELECT FieldValue FROM #myTemp); 
          IF @OldValue IS NULL SET @OldValue = ''
            
          IF @SqlString IS NULL
          SELECT @SqlString = 'UPDATE ' + @MySQLTableName+ ' SET '
                    
          IF @NewValue <> @OldValue
          BEGIN
             SET @HasModifiedCol = 1
            SET @SqlString = @SqlString + @FieldName + ' = ''' + @NewValue + ''', '
          END
        END
        -- remove the last comma
        SELECT @SqlString = LTRIM(REVERSE(@SqlString))
        SELECT @SqlString = REVERSE(SUBSTRING(@SqlString, 2, LEN(@SqlString)))
        SELECT @SqlString = @SqlString + ' WHERE ' + 
                            @IdentityColName + ' = ''' + @IdentityColVal + ''''
        IF @HasModifiedCol = 1
        BEGIN 
            PRINT @SqlString
            EXEC dbo.MySqlDataSync @SqlString
        END
    END
END
GO

Here it is assumed that the MySQL table has the same structure and table fields as the SQL Server table. And a catch here about the trigger is that it can be used for any table without any other changes except for the table names.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

scott_liu
Software Developer (Senior)
Canada Canada
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionCODE SYNCHRONISATIONmemberseemajoshi23105 Oct '12 - 1:04 
I have a database that is working offline .When i connect to the network i want the data from that table to synchronise with the data in the another table on the same database.
Please help me out.
thanks
seema
seemajoshi2310@gmail.com
QuestionBoth on different Servermembersurajjoshi134 May '12 - 22:00 
Hi Sir,
I want to sync data from SQL Server on Windows to MySQL on Linux using web-service built in asp.net as you made using crone job from Linux, automatically at specifid time.
And also whatever changes are made in data in sql server should be reflected in MySQL, even for multiple rows, so is this possible using your this helpful code?
 
Thanks in Advance,
Regards,
Suraj Joshi.
BugDoesn't work for statements that update several rowsmemberstipus26 Jul '11 - 23:49 
I tested using a simple table
 
ID CHARVAL INTVAL
1 'hello' 10
2 'test' 20
3 'tst' 10
 
and a simple SQL UPDATE statement: UPDATE TEST SET INTVAL=INTVAL+1 WHERE INTVAL=10
 
The result table is the following:
 
ID CHARVAL INTVAL
1 'hello' 11
2 'test' 20
3 'tst' 11
 
However, the trigger creates only one SQL statement:
 
UPDATE TEST SET INTVAL = '11' WHERE ID = '3'
 
Only the last modified row gets synchronized !
GeneralRe: Doesn't work for statements that update several rowsmemberscott_liu25 Aug '11 - 9:43 
Yes you're right. It was supposed to work in the situation that users edit a form and submit (insert, update or delete one data row).
 
If you want it to work for multi rows, you may change it to genereate multple DML statements. Try to start it from changing (for insert, notice "SELECT TOP 1", same with update and delete)
 
SET @SqlStringTemp = 'INSERT INTO #myTemp (FieldValue) SELECT TOP 1 CAST(' +
@FieldName + ' AS nvarchar(max)) FROM #myInserted1';
 
and the codes afterwards.
 
Thanks.
GeneralMy vote of 5memberRich Fantozzi5 Jul '11 - 8:31 
Good simply way to connect database via web services.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 16 Aug 2012
Article Copyright 2011 by scott_liu
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid