Трассировка процесса синхронизации

В данном разделе показано, как использовать инфраструктуру трассировки и класс трассировки в службах Sync Framework. В примерах этого раздела описываются следующие типы и события платформы Sync Framework.

Сведения о запуске образца кода см. в подразделе «Образцы приложений в разделах инструкций» раздела Синхронизация SQL Server и SQL Server Compact.

Основные сведения о трассировке в службах Synchronization Services

Трассировка предусматривает регистрацию операций, данных и метаданных приложений, а также передачу этих сведений в прослушиватель. Прослушиватель часто записывает сведения о трассировке в файл, но также может обрабатывать их и другими способами. В службах Sync Framework предусмотрена трассировка для служб синхронизации баз данных. В распределенных приложениях трассировка может быть очень важна, так как она позволяет устранять неполадки, которые в противном случае было бы трудно обнаружить.

В службах Sync Framework средства трассировки состоят из следующих компонентов.

  • Инфраструктура трассировки, основанная на реализации трассировки .NET Framework, в частности на классе TraceListener. Трассировке подвергаются наиболее важные операции поставщиков баз данных, а ключевые метаданные передаются в один или несколько прослушивателей.

  • Объект SyncTracer. Это позволяет определять включенный уровень трассировки, а также записывать сообщения в выходные данные трассировки на основе событий в приложениях.

Кроме компонентов трассировки, которые предоставляют службы Sync Framework, устранение неисправностей обычно включает другие средства, например отладчик или Приложение SQL Server Profiler. Например, выходные данные трассировки могут включать сведения о базе данных SQL Server, а для получения дополнительной информации об операциях с базой данных, выполняемых службой синхронизации сервера, можно использовать Приложение SQL Server Profiler.

Использование инфраструктуры трассировки

По умолчанию для приложений служб Sync Framework трассировка отключена. Чтобы настроить прослушиватель трассировок, поместите файл конфигурации приложения (AppName.exe.config) в ту же папку, где находится исполняемый файл управляемого приложения. Дополнительные сведения об этом файле см. в документации по Visual Studio. В этом файле можно добавить и удалить прослушиватель, установить его тип и связанные параметры или очистить файл от всех ранее установленных приложением прослушивателей. Дополнительные сведения о трассировке см. в документации по .NET Framework. Файл конфигурации должен выглядеть примерно так:

<configuration>
  <system.diagnostics>
    <switches>
      <!--  0-off, 1-error, 2-warn, 3-info, 4-verbose. -->
      <add name="SyncTracer" value="3" />
    </switches>

    <trace autoflush="true">
      <listeners>
        <add name="TestListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="TraceSample.log"/>
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

Следующие XML-узлы включаются в образец кода.

  • Прослушиватель типа System.Diagnostics.TextWriterTraceListener (TextWriterTraceListener), с именем выходного файла TraceSample.log.

  • Переключатель с именем SyncTracer. Он имеет значение 3. В следующей таблице показаны все значения и их связь с выводом трассировки.

    Значение переключателя

    Уровень трассировки

    Выходные данные

    0

    off

    Не направлять сообщения в прослушиватели трассировки.

    1

    error

    Направлять в прослушиватели трассировки только сообщения об ошибках.

    2

    warning

    Направлять в прослушиватели трассировки сообщения об ошибках и предупреждения.

    3

    info

    Направлять в прослушиватели трассировки информационные сообщения, сообщения об ошибках и предупреждения.

    4

    verbose

    Направлять все сообщения в прослушиватели трассировки.

    Трассировка создает некоторую дополнительную нагрузку. Таким образом, следует учитывать, что применение трассировки может повлиять на то, соблюдаются ли требования по производительности приложения. В большинстве случаев параметры info и verbose доступны только во время разработки и устранения неисправностей приложения.

В следующем файле конфигурации показан пример устройства.

<configuration>

   <traceSettings>

     <add key ="FileLocation" value="TraceSample.log"/>

     <add key ="LogLevel" value="4"/>

   </traceSettings>

</configuration>

Файлу необходимо присвоить имя trace.config.txt и сохранить в каталоге приложений на устройстве. При включении файла в решение Visual Studio его можно будет развернуть вместе с приложением.

Следующий сегмент файла взят из трассировки, настроенной на использование значения SyncTracer, равного 3. Каждая строка начинается с выходных данных определенного типа. В данном случае выходными данными являются INFO. В случае возникновения ошибки соответствующие строки будут начинаться с ERROR.

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, соединение с сервером с помощью строки: Data Source=localhost;Initial Catalog=SyncSamplesDb;Integrated Security=True

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, ----- сервер перечисляет изменения на клиенте из группы "Клиент" -----

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, идентификатор клиента: bc5610f6-bf9c-4ccd-8599-839e54e953e2

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, сопоставленный идентификатор инициатора: 0

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:042,

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:042, ----- перечисление вставок в таблицу Customer -----

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:058, перечислено изменений: 5

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:058, --- завершено перечисление вставок в таблицу Customer ---

Использование объекта SyncTracer

Объект SyncTracer позволяет записывать данные трассировки приложения в файл трассировки. Это позволяет получить контекстные сведения о работе приложения в любое конкретное время. Этот объект позволяет выполнить следующие задачи.

  • Определить, какой уровень трассировки включен, с помощью следующих методов.

  • Запись сообщений в выходные данные трассировки в соответствии с событиями приложения с помощью следующих методов, а также других методов, являющихся перегруженными версиями каждого из этих методов:

    Например, для вывода информационного сообщения можно использовать следующий код.

    SyncTracer.Info("Informational message")

    Если включен уровень Info, сообщение будет записано в выходные данные. В противном случае оно пропускается.

    Также можно формировать более сложные сообщения, подобные следующему. Указанные цифры устанавливают уровень отступа в выходном файле.

    SyncTracer.Verbose("Processing table t1")

    SyncTracer.Verbose(1, "Applying Deletes")

    SyncTracer.Verbose(2, "{0} rows deleted", numDeleted)

    SyncTracer.Verbose(1, "Applying Inserts")

    SyncTracer.Verbose(2, "{0} rows inserted", numInserted)

    Выходные данные выглядят следующим образом.

    Processing table t1

    Applying Deletes

    7 Rows Deleted

    Applying Inserts

    9 Rows inserted

Следующий образец кода записывает в консоль данные о том, какие уровни трассировки включены. Файл конфигурации указывает значение 3 для переключателя SyncTracer. Это соответствует Info. Таким образом, Error, Warning, Info возвращают значение типа True, а Verbose возвращает значение типа False.

Console.WriteLine("** Tracing Levels Enabled for this Application **");
Console.WriteLine("Error: " + SyncTracer.IsErrorEnabled().ToString());
Console.WriteLine("Warning: " + SyncTracer.IsWarningEnabled().ToString());
Console.WriteLine("Info: " + SyncTracer.IsInfoEnabled().ToString());
Console.WriteLine("Verbose: " + SyncTracer.IsVerboseEnabled().ToString());
Console.WriteLine("** Tracing Levels Enabled for this Application **")
Console.WriteLine("Error: " + SyncTracer.IsErrorEnabled().ToString())
Console.WriteLine("Warning: " + SyncTracer.IsWarningEnabled().ToString())
Console.WriteLine("Info: " + SyncTracer.IsInfoEnabled().ToString())
Console.WriteLine("Verbose: " + SyncTracer.IsVerboseEnabled().ToString())

В следующем образце кода показано, как записывать отформатированные предупреждения о конфликтах данных в выходные данные трассировки. Дополнительные сведения о конфликтах см. в разделе Как обрабатывать конфликты и ошибки данных для синхронизации базы данных (SQL Server). Подробная трассировка предусматривает получение сведений о конфликтах. В данном приложении подробная трассировка отключена, а вместо этого для выработки предупреждений о конфликтах применяются флаги приложения.

if (SyncTracer.IsVerboseEnabled() == false && e.Conflict.Type != DbConflictType.ErrorsOccurred)
{
    DataTable conflictingClientChange = e.Conflict.LocalChange;
    DataTable conflictingServerChange = e.Conflict.RemoteChange;
    int serverColumnCount = conflictingServerChange.Columns.Count;
    int clientColumnCount = conflictingClientChange.Columns.Count;
    StringBuilder clientRowAsString = new StringBuilder();
    StringBuilder serverRowAsString = new StringBuilder();

    for (int i = 0; i < clientColumnCount; i++)
    {
        clientRowAsString.Append(conflictingClientChange.Rows[0][i] + " | ");
    }

    for (int i = 0; i < serverColumnCount; i++)
    {
        serverRowAsString.Append(conflictingServerChange.Rows[0][i] + " | ");
    }

    SyncTracer.Warning(1, "CONFLICT DETECTED FOR SESSION {0}", e.Session.SessionId);
    SyncTracer.Warning(2, "** Client change **");
    SyncTracer.Warning(2, clientRowAsString.ToString());
    SyncTracer.Warning(2, "** Server change **");
    SyncTracer.Warning(2, serverRowAsString.ToString());
}
If SyncTracer.IsVerboseEnabled() = False AndAlso e.Conflict.Type <> DbConflictType.ErrorsOccurred Then
    Dim conflictingClientChange As DataTable = e.Conflict.LocalChange
    Dim conflictingServerChange As DataTable = e.Conflict.RemoteChange
    Dim serverColumnCount As Integer = conflictingServerChange.Columns.Count
    Dim clientColumnCount As Integer = conflictingClientChange.Columns.Count
    Dim clientRowAsString As New StringBuilder()
    Dim serverRowAsString As New StringBuilder()

    For i As Integer = 0 To clientColumnCount - 1
        clientRowAsString.Append(Convert.ToString(conflictingClientChange.Rows(0)(i)) & " | ")
    Next

    For i As Integer = 0 To serverColumnCount - 1
        serverRowAsString.Append(Convert.ToString(conflictingServerChange.Rows(0)(i)) & " | ")
    Next

    SyncTracer.Warning(1, "CONFLICT DETECTED FOR SESSION {0}", e.Session.SessionId)
    SyncTracer.Warning(2, "** Client change **")
    SyncTracer.Warning(2, clientRowAsString.ToString())
    SyncTracer.Warning(2, "** Server change **")
    SyncTracer.Warning(2, serverRowAsString.ToString())
End If

Проверка того, какие уровни трассировки включены, поможет избежать потенциально дорогостоящей обработки. В этом образце кода приложение позволяет отказаться от дополнительной обработки, если подробная трассировка уже включена. Напротив, приложение может сформировать выходные данные, только если включен определенный уровень трассировки.

Вопросы безопасности трассировки

Файлы трассировки могут содержать сведения о компьютерах (серверных и клиентских), о данных приложений и именах входа. (пароли в файл трассировки не записываются). Если включена подробная трассировка, каждая измененная строка из базы данных записывается в файл трассировки. Помогает защитить файл трассировки соответствующими списками управления доступом.

Полный пример кода

Приведенный ниже полный пример кода содержит ранее описанные в этом разделе примеры и дополнительный код синхронизации. Перед запуском этого приложения выполните следующие шаги.

  1. Создайте проект в Visual Studio.

  2. Добавьте ссылки на динамические библиотеки платформ Sync Framework и класс Utility, расположенный в разделе Инструкции по классу Utility для поставщика базы данных.

  3. Создайте файл конфигурации и скопируйте код XML из примера, приведенного ранее в этом разделе.

Следует учитывать наличие вызовов методов в классе Utility:

  • Utility.MakeFailingChangeOnNode(Utility.ConnStr_SqlSync_Client) Этот метод вносит на клиенте изменение, завершающееся с ошибкой при его применении на сервере. Нарушение ограничения и связанное исключение приложения автоматически записываются в файл трассировки в виде предупреждений.

  • Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Client, "Customer") и Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Server, "Customer") — эти методы вносят на клиенте и сервере изменения, которые конфликтуют при их синхронизации. Сведения о конфликтах записываются в файл трассировки в обработчике события SampleSyncProvider_ApplyChangeFailed.

После запуска приложения откройте выходной файл трассировки для просмотра сообщений, записанных автоматически, и предупреждений о конфликтах, возникших в результате выполнения кода приложения.

using System;
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 void Main(string[] args)
        {
            // Specify the connections to the SQL Server and SQL Server Compact databases.
            SqlConnection serverConn = new SqlConnection(Utility.ConnStr_SqlSync_Server);
            SqlConnection clientConn = new SqlConnection(Utility.ConnStr_SqlSync_Client);

            //Write to the console which tracing levels are enabled. The app.config
            //file specifies a value of 3 for the SyncTracer switch, which corresponds
            //to Info. Therefore, Error, Warning, and Info return True, and Verbose
            //returns False.
            Console.WriteLine("");
            Console.WriteLine("** Tracing Levels Enabled for this Application **");
            Console.WriteLine("Error: " + SyncTracer.IsErrorEnabled().ToString());
            Console.WriteLine("Warning: " + SyncTracer.IsWarningEnabled().ToString());
            Console.WriteLine("Info: " + SyncTracer.IsInfoEnabled().ToString());
            Console.WriteLine("Verbose: " + SyncTracer.IsVerboseEnabled().ToString());

            // Create a scope named "customers" and add a table to it.
            DbSyncScopeDescription scopeDesc = new DbSyncScopeDescription("customers");

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

            scopeDesc.Tables.Add(customerDescription);

            // Create a provisioning object for "customers". We specify that
            // all synchronization-related objects should be created in a 
            // database schema named "Sync". If you specify a schema, it must already exist
            // in the database.
            SqlSyncScopeProvisioning serverConfig = new SqlSyncScopeProvisioning(serverConn, scopeDesc);
            serverConfig.ObjectSchema = "Sync";

            // Configure the scope and change-tracking infrastructure.
            serverConfig.Apply();

            // Provision the client database.
            SqlSyncScopeProvisioning clientConfig = new SqlSyncScopeProvisioning(clientConn, scopeDesc);
            clientConfig.ObjectSchema = "Sync";
            clientConfig.Apply();

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

            // Data is downloaded from the server to the client.
            syncOrchestrator = new SampleSyncOrchestrator(
                new SqlSyncProvider("customers", clientConn, null, "Sync"),
                new SqlSyncProvider("customers", serverConn, null, "Sync")
                );
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "initial");


            //Make a change at the client that fails when it is
            //applied at the servr. The constraint violation
            //is automatically written to the trace file as a warning.
            Utility.MakeFailingChangeOnNode(Utility.ConnStr_SqlSync_Client);

            //Make changes at the client and server that conflict
            //when they are synchronized. The conflicts are written
            //to the trace file in the SampleSyncProvider_ApplyChangeFailed
            //event handler.
            Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Client, "Customer");
            Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Server, "Customer");

            SqlSyncProvider clientProv = new SqlSyncProvider("customers", clientConn, null, "Sync");
            clientProv.ApplyChangeFailed += new EventHandler<DbApplyChangeFailedEventArgs>(SampleSyncProvider_ApplyChangeFailed);

            SqlSyncProvider serverProv = new SqlSyncProvider("customers", serverConn, null, "Sync");
            serverProv.ApplyChangeFailed += new EventHandler<DbApplyChangeFailedEventArgs>(SampleSyncProvider_ApplyChangeFailed);

            // Synchronize the two databases.
            syncOrchestrator = new SampleSyncOrchestrator(clientProv, serverProv);
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "subsequent");

            serverConn.Close();
            serverConn.Dispose();
            clientConn.Close();
            clientConn.Dispose();

            Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Server);
            Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Client);

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

        static void SampleSyncProvider_ApplyChangeFailed(object sender, DbApplyChangeFailedEventArgs e)
        {
            //Verbose tracing includes information about conflicts. In this application,
            //Verbose tracing is disabled, and we have decided to flag conflicts with a 
            //warning.
            //Check if Verbose tracing is enabled and if the conflict is an error.
            //If the conflict is not an error, we write a warning to the trace file
            //with information about the conflict.
            if (SyncTracer.IsVerboseEnabled() == false && e.Conflict.Type != DbConflictType.ErrorsOccurred)
            {
                DataTable conflictingClientChange = e.Conflict.LocalChange;
                DataTable conflictingServerChange = e.Conflict.RemoteChange;
                int serverColumnCount = conflictingServerChange.Columns.Count;
                int clientColumnCount = conflictingClientChange.Columns.Count;
                StringBuilder clientRowAsString = new StringBuilder();
                StringBuilder serverRowAsString = new StringBuilder();

                for (int i = 0; i < clientColumnCount; i++)
                {
                    clientRowAsString.Append(conflictingClientChange.Rows[0][i] + " | ");
                }

                for (int i = 0; i < serverColumnCount; i++)
                {
                    serverRowAsString.Append(conflictingServerChange.Rows[0][i] + " | ");
                }

                SyncTracer.Warning(1, "CONFLICT DETECTED FOR SESSION {0}", e.Session.SessionId);
                SyncTracer.Warning(2, "** Client change **");
                SyncTracer.Warning(2, clientRowAsString.ToString());
                SyncTracer.Warning(2, "** Server change **");
                SyncTracer.Warning(2, serverRowAsString.ToString());
            }
        }
    }

    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);
        }
    }
}
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
        Public Shared Sub Main(ByVal args As String())
            ' Specify the connections to the SQL Server and SQL Server Compact databases.
            Dim serverConn As New SqlConnection(Utility.ConnStr_SqlSync_Server)
            Dim clientConn As New SqlConnection(Utility.ConnStr_SqlSync_Client)

            'Write to the console which tracing levels are enabled. The app.config
            'file specifies a value of 3 for the SyncTracer switch, which corresponds
            'to Info. Therefore, Error, Warning, and Info return True, and Verbose
            'returns False.
            Console.WriteLine("")
            Console.WriteLine("** Tracing Levels Enabled for this Application **")
            Console.WriteLine("Error: " + SyncTracer.IsErrorEnabled().ToString())
            Console.WriteLine("Warning: " + SyncTracer.IsWarningEnabled().ToString())
            Console.WriteLine("Info: " + SyncTracer.IsInfoEnabled().ToString())
            Console.WriteLine("Verbose: " + SyncTracer.IsVerboseEnabled().ToString())

            ' Create a scope named "customers" and add a table to it.
            Dim scopeDesc As New DbSyncScopeDescription("customers")

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

            scopeDesc.Tables.Add(customerDescription)

            ' Create a provisioning object for "customers". We specify that
            ' all synchronization-related objects should be created in a 
            ' database schema named "Sync". If you specify a schema, it must already exist
            ' in the database.
            Dim serverConfig As New SqlSyncScopeProvisioning(serverConn, scopeDesc)
            serverConfig.ObjectSchema = "Sync"

            ' Configure the scope and change-tracking infrastructure.
            serverConfig.Apply()

            ' Provision the client database.
            Dim clientConfig As New SqlSyncScopeProvisioning(clientConn, scopeDesc)
            clientConfig.ObjectSchema = "Sync"
            clientConfig.Apply()

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

            ' Data is downloaded from the server to the client.
            syncOrchestrator = New SampleSyncOrchestrator(New SqlSyncProvider("customers", clientConn, Nothing, "Sync"), New SqlSyncProvider("customers", serverConn, Nothing, "Sync"))
            syncStats = syncOrchestrator.Synchronize()
            syncOrchestrator.DisplayStats(syncStats, "initial")


            'Make a change at the client that fails when it is
            'applied at the servr. The constraint violation
            'is automatically written to the trace file as a warning.
            Utility.MakeFailingChangeOnNode(Utility.ConnStr_SqlSync_Client)

            'Make changes at the client and server that conflict
            'when they are synchronized. The conflicts are written
            'to the trace file in the SampleSyncProvider_ApplyChangeFailed
            'event handler.
            Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Client, "Customer")
            Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Server, "Customer")

            Dim clientProv As New SqlSyncProvider("customers", clientConn, Nothing, "Sync")
            AddHandler clientProv.ApplyChangeFailed, AddressOf SampleSyncProvider_ApplyChangeFailed

            Dim serverProv As New SqlSyncProvider("customers", serverConn, Nothing, "Sync")
            AddHandler serverProv.ApplyChangeFailed, AddressOf SampleSyncProvider_ApplyChangeFailed

            ' Synchronize the two databases.
            syncOrchestrator = New SampleSyncOrchestrator(clientProv, serverProv)
            syncStats = syncOrchestrator.Synchronize()
            syncOrchestrator.DisplayStats(syncStats, "subsequent")

            serverConn.Close()
            serverConn.Dispose()
            clientConn.Close()
            clientConn.Dispose()

            Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Server)
            Utility.CleanUpSqlNode(Utility.ConnStr_SqlSync_Client)

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

        Private Shared Sub SampleSyncProvider_ApplyChangeFailed(ByVal sender As Object, ByVal e As DbApplyChangeFailedEventArgs)
            'Verbose tracing includes information about conflicts. In this application,
            'Verbose tracing is disabled, and we have decided to flag conflicts with a 
            'warning.
            'Check if Verbose tracing is enabled and if the conflict is an error.
            'If the conflict is not an error, we write a warning to the trace file
            'with information about the conflict.
            If SyncTracer.IsVerboseEnabled() = False AndAlso e.Conflict.Type <> DbConflictType.ErrorsOccurred Then
                Dim conflictingClientChange As DataTable = e.Conflict.LocalChange
                Dim conflictingServerChange As DataTable = e.Conflict.RemoteChange
                Dim serverColumnCount As Integer = conflictingServerChange.Columns.Count
                Dim clientColumnCount As Integer = conflictingClientChange.Columns.Count
                Dim clientRowAsString As New StringBuilder()
                Dim serverRowAsString As New StringBuilder()

                For i As Integer = 0 To clientColumnCount - 1
                    clientRowAsString.Append(Convert.ToString(conflictingClientChange.Rows(0)(i)) & " | ")
                Next

                For i As Integer = 0 To serverColumnCount - 1
                    serverRowAsString.Append(Convert.ToString(conflictingServerChange.Rows(0)(i)) & " | ")
                Next

                SyncTracer.Warning(1, "CONFLICT DETECTED FOR SESSION {0}", e.Session.SessionId)
                SyncTracer.Warning(2, "** Client change **")
                SyncTracer.Warning(2, clientRowAsString.ToString())
                SyncTracer.Warning(2, "** Server change **")
                SyncTracer.Warning(2, serverRowAsString.ToString())
            End If
        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

См. также

Другие ресурсы

Рекомендации по разработке и развертыванию приложений