How to: Intercept and Change Data During Synchronization

This topic describes how to intercept and change data that is sent or received during synchronization. This technique can be used to enforce business rules by making changes to data as it goes in and out of a database. The code in this topic focuses on the following Sync Framework classes and events:

ChangesSelected

ChangesApplied

For more information about how to run the sample code, see "Example Applications in the How to Topics" in Synchronizing SQL Server and SQL Server Compact.

Understanding Business Rules

Many businesses have particular rules to apply to data that flows into and out of their databases. Because these rules are particular to your business, Sync Framework offers several events that you can use to intercept the data that flows during synchronization. By handling these events you can interject your own code to enforce whatever business rules you require. The following table lists the most common events to use for business rules, and when they occur during synchronization:

Event

When does it occur?

ChangesSelected

Occurs when the provider is the source provider, after enumerating changes but before sending the changes to the destination provider.

ApplyingChanges

Occurs when the provider is the destination provider, after connecting to the database but before changes are applied.

ChangesApplied

Occurs when the provider is the destination provider, after changes are applied but before committing the transaction and disconnecting from the database.

Be aware that the SqlSyncProvider class does not override these events, so when you use SqlSyncProvider you use the events directly from the RelationalSyncProvider base class. The SqlCeSyncProvider class does override these events, so when you use the SqlCeSyncProvider you use the overridden versions. The time when the events occur during synchronization is the same in both cases.

Logging Enumerated Changes in the ChangesSelected Event

Handling the ChangesSelected event allows you to examine the data that is about to be sent from a database during synchronization, before the data is sent. An example of an action to take in this event handler is to log the data that goes out of a server database so that you can later examine exactly what data was sent to which client databases. Alternately, you could send notification to another system, such as telling a warehouse to ship product when a sale is completed.

Before you can handle the event, you must register an event handler with the SqlSyncProvider class to handle the ChangesSelected event. The following code example shows how to do this:

AddHandler serverProvider.ChangesSelected, AddressOf serverProvider_ChangesSelected
serverProvider.ChangesSelected += new EventHandler<DbChangesSelectedEventArgs>(serverProvider_ChangesSelected);

The following code example shows how to handle the ChangesSelected event to log the data that is about to be sent.

    Private Shared Sub serverProvider_ChangesSelected(ByVal sender As Object, ByVal e As DbChangesSelectedEventArgs)
        ' Log the changes to the console. CurrentClientDatabase is set by the application, which knows
        ' the identity of both databases in the session.
        Console.WriteLine("About to send changes to the {0} database." & vbLf, CurrentClientDatabase)

        For Each table As DataTable In e.Context.DataSet.Tables
            Console.WriteLine(vbTab & "Table: {0}", table.TableName)
            If table.Rows.Count = 0 Then
                Console.WriteLine(vbTab & vbTab & "No rows changed.")
            Else
                For Each row As DataRow In table.Rows
                    ' Write the row state, which indicates insert, update, or delete, and the row data.
                    Console.Write(vbTab & vbTab & "{0}: ", row.RowState)
                    For Each item As Object In row.ItemArray
                        Console.Write("{0}, ", item.ToString())
                    Next
                    Console.WriteLine()
                Next
            End If
        Next
    End Sub
End Class
    static void serverProvider_ChangesSelected(object sender, DbChangesSelectedEventArgs e)
    {
        // Log the changes to the console. CurrentClientDatabase is set by the application, which knows
        // the identity of both databases in the session.
        Console.WriteLine("About to send changes to the {0} database.\n", CurrentClientDatabase);

        foreach (DataTable table in e.Context.DataSet.Tables)
        {
            Console.WriteLine("\tTable: {0}", table.TableName);
            if (table.Rows.Count == 0)
            {
                Console.WriteLine("\t\tNo rows changed.");
            }
            else
            {
                foreach (DataRow row in table.Rows)
                {
                    // Write the row state, which indicates insert, update, or delete, and the row data.
                    Console.Write("\t\t{0}: ", row.RowState);
                    foreach (object item in row.ItemArray)
                    {
                        Console.Write("{0}, ", item.ToString());
                    }
                    Console.WriteLine();
                }
            }
        }
    }
}

Changing Data in the ChangesApplied Event

Handling the ChangesApplied event allows you to examine the data that has just been applied to the database, but before the transaction has been committed. An example of an action to take in this event handler is to fix up data that has been formatted incorrectly. Because a change made to data in this event occurs after the change from the client has been applied, the change is treated as a local change and is stamped with a version that indicates that it is not contained in the client database. Therefore, next time the client performs a download synchronization with the server, it will receive the fixed data.

Before you can handle the event, you must register an event handler with the SqlSyncProvider class to handle the ChangesApplied event. The following code example shows how to do this:

AddHandler serverProvider.ChangesApplied, AddressOf serverProvider_ChangesApplied
serverProvider.ChangesApplied += new EventHandler<DbChangesAppliedEventArgs>(serverProvider_ChangesApplied);

The following code example shows how to examine changed rows in a customer contact table and build a SQL UPDATE command that fixes the formatting of incorrectly formatted phone numbers. The change is made by using an IDbCommand object that uses the current connection and transaction, but makes the change outside of the Sync Framework API so that the change is treated as a local change and receives a new version stamp.

Private Shared Sub serverProvider_ChangesApplied(ByVal sender As Object, ByVal e As DbChangesAppliedEventArgs)
    Dim doExecute As Boolean = False
    Dim cmdText As String = ""

    For Each row As DataRow In e.Context.DataSet.Tables("Sales.CustomerContact").Rows
        ' This only fixes numbers formatted as (555)555-5555 to 555-555-5555.
        ' Build the UPDATE statement.
        Dim phoneNumber As String = DirectCast(row("PhoneNumber"), String)
        If phoneNumber(0) = "("c Then
            Dim newPhoneNumber As String = phoneNumber.Remove(0, 1)
            newPhoneNumber = newPhoneNumber.Replace(")"c, "-"c)

            cmdText += [String].Format("UPDATE Sales.CustomerContact SET PhoneNumber = '{0}' WHERE PhoneNumber = '{1}' ", newPhoneNumber, phoneNumber)

            doExecute = True
        End If
    Next

    If doExecute Then
        ' Create a command that operates on the open connection and use the current transaction to execute it.
        ' Sync Framework commits this transaction in later processing so it should not be committed here.
        Dim cmd As IDbCommand = e.Connection.CreateCommand()
        cmd.Transaction = e.Transaction
        cmd.CommandText = cmdText
        cmd.ExecuteNonQuery()
    End If
End Sub
static void serverProvider_ChangesApplied(object sender, DbChangesAppliedEventArgs e)
{
    bool doExecute = false;
    string cmdText = "";

    foreach (DataRow row in e.Context.DataSet.Tables["Sales.CustomerContact"].Rows)
    {
        // This only fixes numbers formatted as (555)555-5555 to 555-555-5555.
        // Build the UPDATE statement.
        string phoneNumber = (string)row["PhoneNumber"];
        if (phoneNumber[0] == '(')
        {
            string newPhoneNumber = phoneNumber.Remove(0, 1);
            newPhoneNumber = newPhoneNumber.Replace(')', '-');

            cmdText += String.Format("UPDATE Sales.CustomerContact SET PhoneNumber = '{0}' WHERE PhoneNumber = '{1}' ", 
                newPhoneNumber, phoneNumber);

            doExecute = true;
        }
    }

    if (doExecute)
    {
        // Create a command that operates on the open connection and use the current transaction to execute it.
        // Sync Framework commits this transaction in later processing so it should not be committed here.
        IDbCommand cmd = e.Connection.CreateCommand();
        cmd.Transaction = e.Transaction;
        cmd.CommandText = cmdText;
        cmd.ExecuteNonQuery();
    }
}

Complete Code Example

The following complete code example includes the code examples that are described earlier and additional code to configure and clean up the databases. The example requires the Utility class that is available in Utility Class for Database Provider How-to Topics.

Imports System.Collections.ObjectModel
Imports System.IO
Imports System.Text
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlServerCe
Imports Microsoft.Synchronization
Imports Microsoft.Synchronization.Data
Imports Microsoft.Synchronization.Data.SqlServer
Imports Microsoft.Synchronization.Data.SqlServerCe

Namespace Microsoft.Samples.Synchronization
    Class Program
        Shared CurrentClientDatabase As String = ""

        Shared Sub Main(ByVal args As String())
            ' Create connections.
            Dim serverConn As New SqlConnection(Utility.ConnStr_SqlSync_Server)
            Dim clientSqlCe1Conn As New SqlCeConnection(Utility.ConnStr_SqlCeSync1)

            ' Create "customers" scope and provision the server database.
            Dim scopeDesc As New DbSyncScopeDescription("customers")

            Dim customerDescription As DbSyncTableDescription = SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.Customer", serverConn)

            scopeDesc.Tables.Add(customerDescription)

            Dim customerContactDescription As DbSyncTableDescription = SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.CustomerContact", serverConn)

            scopeDesc.Tables.Add(customerContactDescription)

            Dim serverConfig As New SqlSyncScopeProvisioning(serverConn, scopeDesc)
            serverConfig.ObjectSchema = "Sync"

            serverConfig.Apply()

            ' Provision one client database.
            Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync1, True)
            Dim clientSqlDesc As DbSyncScopeDescription = SqlSyncDescriptionBuilder.GetDescriptionForScope("customers", Nothing, "Sync", serverConn)
            Dim clientSqlCe1Config As New SqlCeSyncScopeProvisioning(clientSqlCe1Conn, clientSqlDesc)
            clientSqlCe1Config.ObjectPrefix = "Sync"
            clientSqlCe1Config.Apply()

            ' Initial synchronization session.
            Dim syncOrchestrator As SampleSyncOrchestrator
            Dim syncStats As SyncOperationStatistics

            ' Add event handlers to the server provider.
            ' ApplyingChanges intercepts and modifies incoming items before they are applied to the server database.
            ' ChangesSelected intercepts and logs changes before they are sent to clients.
            Dim serverProvider As New SqlSyncProvider("customers", serverConn, Nothing, "Sync")
            AddHandler serverProvider.ChangesApplied, AddressOf serverProvider_ChangesApplied
            AddHandler serverProvider.ChangesSelected, AddressOf serverProvider_ChangesSelected

            ' Create client provider.
            Dim clientSqlCe1Provider As New SqlCeSyncProvider("customers", clientSqlCe1Conn, "Sync")

            ' Synchronize with the two client databases.
            CurrentClientDatabase = "SqlCe1"
            syncOrchestrator = New SampleSyncOrchestrator(clientSqlCe1Provider, serverProvider)
            syncStats = syncOrchestrator.Synchronize()
            syncOrchestrator.DisplayStats(syncStats, "initial")



            ' Make a change on client1 that contains a poorly formatted phone number.
            ' This will be fixed up by the server ApplyingChanges event handler.
            Dim sqlCeCommand As SqlCeCommand = clientSqlCe1Conn.CreateCommand()

            clientSqlCe1Conn.Open()

            sqlCeCommand.CommandText = "INSERT INTO Customer (CustomerId, CustomerName, SalesPerson, CustomerType) " & "VALUES ('D4053369-E3CE-46F7-AFA2-4C29CB45525B', 'Cycle Mart', 'James Bailey', 'Retail')"
            sqlCeCommand.ExecuteNonQuery()

            sqlCeCommand.CommandText = "INSERT INTO CustomerContact (CustomerId, PhoneNumber, PhoneType) " & "VALUES ('D4053369-E3CE-46F7-AFA2-4C29CB45525B', '(453)555-0152', 'Mobile') "
            sqlCeCommand.ExecuteNonQuery()

            sqlCeCommand.CommandText = "INSERT INTO CustomerContact (CustomerId, PhoneNumber, PhoneType) " & "VALUES ('D4053369-E3CE-46F7-AFA2-4C29CB45525B', '(453)555-0001', 'Business') "
            sqlCeCommand.ExecuteNonQuery()

            sqlCeCommand.CommandText = "INSERT INTO Customer (CustomerId, CustomerName, SalesPerson, CustomerType) " & "VALUES ('009aa4b6-3433-4136-ad9a-a7e1bb2528f7', 'Cycle Merchants', 'James Bailey', 'Wholesale')"
            sqlCeCommand.ExecuteNonQuery()

            clientSqlCe1Conn.Close()

            ' Upload from client1 to server first uploads the new phone number.
            ' Download from server to client1 downloads the fixed phone number.
            CurrentClientDatabase = "SqlCe1"
            syncOrchestrator = New SampleSyncOrchestrator(clientSqlCe1Provider, serverProvider)
            syncStats = syncOrchestrator.Synchronize()
            syncOrchestrator.DisplayStats(syncStats, "subsequent")


            serverConn.Close()
            serverConn.Dispose()
            clientSqlCe1Conn.Close()
            clientSqlCe1Conn.Dispose()

            Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Server)

            Console.Write(vbLf & "Press any key to exit.")
            Console.Read()
        End Sub

        ' Handle the ChangesApplied event on the server. This event occurs after changes are received from
        ' the client and after they are applied to the server database. This handler fixes up poorly formatted
        ' phone numbers and applies the fixed rows as new changes. By applying them as new changes, the version is 
        ' incremented so that subsequent downloads will synchronize the fixed rows back to the client.
        Private Shared Sub serverProvider_ChangesApplied(ByVal sender As Object, ByVal e As DbChangesAppliedEventArgs)
            Dim doExecute As Boolean = False
            Dim cmdText As String = ""

            For Each row As DataRow In e.Context.DataSet.Tables("Sales.CustomerContact").Rows
                ' This only fixes numbers formatted as (555)555-5555 to 555-555-5555.
                ' Build the UPDATE statement.
                Dim phoneNumber As String = DirectCast(row("PhoneNumber"), String)
                If phoneNumber(0) = "("c Then
                    Dim newPhoneNumber As String = phoneNumber.Remove(0, 1)
                    newPhoneNumber = newPhoneNumber.Replace(")"c, "-"c)

                    cmdText += [String].Format("UPDATE Sales.CustomerContact SET PhoneNumber = '{0}' WHERE PhoneNumber = '{1}' ", newPhoneNumber, phoneNumber)

                    doExecute = True
                End If
            Next

            If doExecute Then
                ' Create a command that operates on the open connection and use the current transaction to execute it.
                ' Sync Framework commits this transaction in later processing so it should not be committed here.
                Dim cmd As IDbCommand = e.Connection.CreateCommand()
                cmd.Transaction = e.Transaction
                cmd.CommandText = cmdText
                cmd.ExecuteNonQuery()
            End If
        End Sub

        ' Handle the ChangesSelected event on the server. This event occurs after changes are selected to
        ' send to a client but before they are sent. This handler logs the changes that are sent and which
        ' client they are sent to.
        Private Shared Sub serverProvider_ChangesSelected(ByVal sender As Object, ByVal e As DbChangesSelectedEventArgs)
            ' Log the changes to the console. CurrentClientDatabase is set by the application, which knows
            ' the identity of both databases in the session.
            Console.WriteLine("About to send changes to the {0} database." & vbLf, CurrentClientDatabase)

            For Each table As DataTable In e.Context.DataSet.Tables
                Console.WriteLine(vbTab & "Table: {0}", table.TableName)
                If table.Rows.Count = 0 Then
                    Console.WriteLine(vbTab & vbTab & "No rows changed.")
                Else
                    For Each row As DataRow In table.Rows
                        ' Write the row state, which indicates insert, update, or delete, and the row data.
                        Console.Write(vbTab & vbTab & "{0}: ", row.RowState)
                        For Each item As Object In row.ItemArray
                            Console.Write("{0}, ", item.ToString())
                        Next
                        Console.WriteLine()
                    Next
                End If
            Next
        End Sub
    End Class

    Public Class SampleSyncOrchestrator
        Inherits SyncOrchestrator
        Public Sub New(ByVal localProvider As RelationalSyncProvider, ByVal remoteProvider As RelationalSyncProvider)

            Me.LocalProvider = localProvider
            Me.RemoteProvider = remoteProvider
            Me.Direction = SyncDirectionOrder.UploadAndDownload
        End Sub

        Public Sub DisplayStats(ByVal syncStatistics As SyncOperationStatistics, ByVal syncType As String)
            Console.WriteLine([String].Empty)
            If syncType = "initial" Then
                Console.WriteLine("****** Initial Synchronization ******")
            ElseIf syncType = "subsequent" Then
                Console.WriteLine("***** Subsequent Synchronization ****")
            End If

            Console.WriteLine("Start Time: " & Convert.ToString(syncStatistics.SyncStartTime))
            Console.WriteLine("Total Changes Uploaded: " & Convert.ToString(syncStatistics.UploadChangesTotal))
            Console.WriteLine("Total Changes Downloaded: " & Convert.ToString(syncStatistics.DownloadChangesTotal))
            Console.WriteLine("Complete Time: " & Convert.ToString(syncStatistics.SyncEndTime))
            Console.WriteLine([String].Empty)
        End Sub
    End Class
End Namespace
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization.Data.SqlServer;
using Microsoft.Synchronization.Data.SqlServerCe;

namespace Microsoft.Samples.Synchronization
{
    class Program
    {
        static string CurrentClientDatabase = "";

        static void Main(string[] args)
        {
            // Create connections.
            SqlConnection serverConn = new SqlConnection(Utility.ConnStr_SqlSync_Server);
            SqlCeConnection clientSqlCe1Conn = new SqlCeConnection(Utility.ConnStr_SqlCeSync1);

            // Create "customers" scope and provision the server database.
            DbSyncScopeDescription scopeDesc = new DbSyncScopeDescription("customers");

            DbSyncTableDescription customerDescription =
                SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.Customer", serverConn);

            scopeDesc.Tables.Add(customerDescription);

            DbSyncTableDescription customerContactDescription =
                SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.CustomerContact", serverConn);

            scopeDesc.Tables.Add(customerContactDescription);

            SqlSyncScopeProvisioning serverConfig = new SqlSyncScopeProvisioning(serverConn, scopeDesc);
            serverConfig.ObjectSchema = "Sync";

            serverConfig.Apply();

            // Provision one client database.
            Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync1, true);
            DbSyncScopeDescription clientSqlDesc = SqlSyncDescriptionBuilder.GetDescriptionForScope("customers", null, "Sync", serverConn);
            SqlCeSyncScopeProvisioning clientSqlCe1Config = new SqlCeSyncScopeProvisioning(clientSqlCe1Conn, clientSqlDesc);
            clientSqlCe1Config.ObjectPrefix = "Sync";
            clientSqlCe1Config.Apply();

            // Initial synchronization session.
            SampleSyncOrchestrator syncOrchestrator;
            SyncOperationStatistics syncStats;

            // Add event handlers to the server provider.
            // ApplyingChanges intercepts and modifies incoming items before they are applied to the server database.
            // ChangesSelected intercepts and logs changes before they are sent to clients.
            SqlSyncProvider serverProvider = new SqlSyncProvider("customers", serverConn, null, "Sync");
            serverProvider.ChangesApplied += new EventHandler<DbChangesAppliedEventArgs>(serverProvider_ChangesApplied);
            serverProvider.ChangesSelected += new EventHandler<DbChangesSelectedEventArgs>(serverProvider_ChangesSelected);

            // Create client provider.
            SqlCeSyncProvider clientSqlCe1Provider = new SqlCeSyncProvider("customers", clientSqlCe1Conn, "Sync");

            // Synchronize with the two client databases.
            CurrentClientDatabase = "SqlCe1";
            syncOrchestrator = new SampleSyncOrchestrator(
                clientSqlCe1Provider,
                serverProvider
                );
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "initial");



            // Make a change on client1 that contains a poorly formatted phone number.
            // This will be fixed up by the server ApplyingChanges event handler.
            SqlCeCommand sqlCeCommand = clientSqlCe1Conn.CreateCommand();

            clientSqlCe1Conn.Open();

            sqlCeCommand.CommandText =
            "INSERT INTO Customer (CustomerId, CustomerName, SalesPerson, CustomerType) " +
            "VALUES ('D4053369-E3CE-46F7-AFA2-4C29CB45525B', 'Cycle Mart', 'James Bailey', 'Retail')";
            sqlCeCommand.ExecuteNonQuery();

            sqlCeCommand.CommandText = 
            "INSERT INTO CustomerContact (CustomerId, PhoneNumber, PhoneType) " +
            "VALUES ('D4053369-E3CE-46F7-AFA2-4C29CB45525B', '(453)555-0152', 'Mobile') ";
            sqlCeCommand.ExecuteNonQuery();

            sqlCeCommand.CommandText =
            "INSERT INTO CustomerContact (CustomerId, PhoneNumber, PhoneType) " +
            "VALUES ('D4053369-E3CE-46F7-AFA2-4C29CB45525B', '(453)555-0001', 'Business') ";
            sqlCeCommand.ExecuteNonQuery();

            sqlCeCommand.CommandText =
            "INSERT INTO Customer (CustomerId, CustomerName, SalesPerson, CustomerType) " +
            "VALUES ('009aa4b6-3433-4136-ad9a-a7e1bb2528f7', 'Cycle Merchants', 'James Bailey', 'Wholesale')";
            sqlCeCommand.ExecuteNonQuery();

            clientSqlCe1Conn.Close();

            // Upload from client1 to server first uploads the new phone number.
            // Download from server to client1 downloads the fixed phone number.
            CurrentClientDatabase = "SqlCe1";
            syncOrchestrator = new SampleSyncOrchestrator(
                clientSqlCe1Provider,
                serverProvider
                );
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "subsequent");


            serverConn.Close();
            serverConn.Dispose();
            clientSqlCe1Conn.Close();
            clientSqlCe1Conn.Dispose();

            Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Server);

            Console.Write("\nPress any key to exit.");
            Console.Read();
        }

        // Handle the ChangesApplied event on the server. This event occurs after changes are received from
        // the client and after they are applied to the server database. This handler fixes up poorly formatted
        // phone numbers and applies the fixed rows as new changes. By applying them as new changes, the version is 
        // incremented so that subsequent downloads will synchronize the fixed rows back to the client.
        static void serverProvider_ChangesApplied(object sender, DbChangesAppliedEventArgs e)
        {
            bool doExecute = false;
            string cmdText = "";

            foreach (DataRow row in e.Context.DataSet.Tables["Sales.CustomerContact"].Rows)
            {
                // This only fixes numbers formatted as (555)555-5555 to 555-555-5555.
                // Build the UPDATE statement.
                string phoneNumber = (string)row["PhoneNumber"];
                if (phoneNumber[0] == '(')
                {
                    string newPhoneNumber = phoneNumber.Remove(0, 1);
                    newPhoneNumber = newPhoneNumber.Replace(')', '-');

                    cmdText += String.Format("UPDATE Sales.CustomerContact SET PhoneNumber = '{0}' WHERE PhoneNumber = '{1}' ", 
                        newPhoneNumber, phoneNumber);

                    doExecute = true;
                }
            }

            if (doExecute)
            {
                // Create a command that operates on the open connection and use the current transaction to execute it.
                // Sync Framework commits this transaction in later processing so it should not be committed here.
                IDbCommand cmd = e.Connection.CreateCommand();
                cmd.Transaction = e.Transaction;
                cmd.CommandText = cmdText;
                cmd.ExecuteNonQuery();
            }
        }

        // Handle the ChangesSelected event on the server. This event occurs after changes are selected to
        // send to a client but before they are sent. This handler logs the changes that are sent and which
        // client they are sent to.
        static void serverProvider_ChangesSelected(object sender, DbChangesSelectedEventArgs e)
        {
            // Log the changes to the console. CurrentClientDatabase is set by the application, which knows
            // the identity of both databases in the session.
            Console.WriteLine("About to send changes to the {0} database.\n", CurrentClientDatabase);

            foreach (DataTable table in e.Context.DataSet.Tables)
            {
                Console.WriteLine("\tTable: {0}", table.TableName);
                if (table.Rows.Count == 0)
                {
                    Console.WriteLine("\t\tNo rows changed.");
                }
                else
                {
                    foreach (DataRow row in table.Rows)
                    {
                        // Write the row state, which indicates insert, update, or delete, and the row data.
                        Console.Write("\t\t{0}: ", row.RowState);
                        foreach (object item in row.ItemArray)
                        {
                            Console.Write("{0}, ", item.ToString());
                        }
                        Console.WriteLine();
                    }
                }
            }
        }
    }

    public class SampleSyncOrchestrator : SyncOrchestrator
    {
        public SampleSyncOrchestrator(RelationalSyncProvider localProvider, RelationalSyncProvider remoteProvider)
        {

            this.LocalProvider = localProvider;
            this.RemoteProvider = remoteProvider;
            this.Direction = SyncDirectionOrder.UploadAndDownload;
        }

        public void DisplayStats(SyncOperationStatistics syncStatistics, string syncType)
        {
            Console.WriteLine(String.Empty);
            if (syncType == "initial")
            {
                Console.WriteLine("****** Initial Synchronization ******");
            }
            else if (syncType == "subsequent")
            {
                Console.WriteLine("***** Subsequent Synchronization ****");
            }

            Console.WriteLine("Start Time: " + syncStatistics.SyncStartTime);
            Console.WriteLine("Total Changes Uploaded: " + syncStatistics.UploadChangesTotal);
            Console.WriteLine("Total Changes Downloaded: " + syncStatistics.DownloadChangesTotal);
            Console.WriteLine("Complete Time: " + syncStatistics.SyncEndTime);
            Console.WriteLine(String.Empty);
        }
    }
}

See Also

Concepts

Advanced Synchronization Scenarios for SQL Server and SQL Server Compact