Export (0) Print
Expand All
This topic has not yet been rated - Rate this topic

Tracing the Synchronization Process

This topic shows how to use the tracing infrastructure and tracing class in Sync Framework. The examples in this topic focus on the following Sync Framework types and events:

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

Tracing involves recording application operations, data, and metadata, and providing this information to a listener. A listener frequently writes trace information to a file, but could also handle the information in other ways. Sync Framework includes tracing for the database synchronization providers. In distributed applications, tracing can be very important because it enables you to troubleshoot issues that might otherwise be difficult to discover.

Tracing in Sync Framework is composed of the following components:

  • A tracing infrastructure that is based on the .NET Framework implementation of tracing, specifically the TraceListener class. The most important operations of the database providers are traced, and key metadata is provided to one or more listeners.

  • The SyncTracer object. This enables you to determine which level of tracing is enabled and to write messages to the trace output based on application events.

In addition to the tracing components that Sync Framework provides, troubleshooting typically involves other tools, such as a debugger or SQL Server Profiler. For example, trace output could include information about a SQL Server database, and then you would use SQL Server Profiler to obtain more detail about database activity that was generated by the server synchronization provider.

Using the Tracing Infrastructure

By default, tracing is not enabled for Sync Framework applications. To configure a trace listener, include an application configuration (AppName.exe.config) file in the same folder as your managed application executable. For more information about this file, see the Visual Studio documentation. In this file, you can add a listener, set its type and related parameters, remove a listener, or clear all the listeners that were previously set by the application. For more information, see the .NET Framework documentation about tracing. The configuration file should resemble the following example.

<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>

The following XML nodes are included in the example code:

  • A listener of type System.Diagnostics.TextWriterTraceListener (TextWriterTraceListener), with the output file name: TraceSample.log.

  • A switch called SyncTracer. This has a value of 3. The following table shows all the values and how they relate to trace output.

    Switch value

    Tracing level

    Output

    0

    off

    No messages to trace listeners.

    1

    error

    Only error messages to trace listeners.

    2

    warning

    Error and warning messages to trace listeners.

    3

    info

    Informational, warning, and error messages to trace listeners.

    4

    verbose

    All messages to trace listeners.

    Tracing does have some overhead. Therefore, you should balance tracing against the performance requirements of your application. In most cases, info and verbose settings are only appropriate during application development and troubleshooting.

The following configuration file shows an example for devices.

<configuration>

   <traceSettings>

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

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

   </traceSettings>

</configuration>

The file should be named trace.config.txt and should be placed in the application directory on the device. If you include the file in a Visual Studio solution, it can be deployed with the application.

The following file segment is from a trace that was configured by using a SyncTracer value of 3. Each line begins with the kind of output. In this case, all the output is INFO. If an error occurred, the relevant lines would begin with ERROR.

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, Connecting to server using string: Data Source=localhost;Initial Catalog=SyncSamplesDb;Integrated Security=True

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, ----- Server Enumerating Changes to Client for Group "Customer" -----

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, Client Id: bc5610f6-bf9c-4ccd-8599-839e54e953e2

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:027, Mapped Originator Id: 0

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

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:042, ----- Enumerating Inserts for Table Customer -----

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:058, Changes Enumerated: 5

INFO, MyApp.vshost, 10, 07/01/2010 17:43:03:058, --- End Enumerating Inserts for Table Customer ---

Using the SyncTracer Object

The SyncTracer object enables you to write application-specific tracing data to the trace file. This can provide contextual information about what an application is doing at a specific time. This object enables you to perform the following tasks:

  • Determine which level of tracing is enabled, by using the following methods:

  • Write messages to the trace output based on application events, by using the following methods, and other overloads for each method:

    For example, to output an informational message, you can use the following code:

    SyncTracer.Info("Informational message")

    If the Info level is enabled, the message is written to the output; otherwise, it is ignored.

    More complex messages can be formed also, such as the following. The numbers that are specified set the level of indentation in the output file.

    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)

    The output is as follows:

    Processing table t1

    Applying Deletes

    7 Rows Deleted

    Applying Inserts

    9 Rows inserted

The following code example writes information to the console about which tracing levels are enabled. The configuration file specifies a value of 3 for the SyncTracer switch. This corresponds to Info. Therefore, Error, Warning, and Info return True, and Verbose returns 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())

The following code example shows how to write formatted warning messages about data conflicts to the trace output. For more information about conflicts, see How to: Handle Data Conflicts and Errors for Database Synchronization (SQL Server). Verbose tracing includes information about conflicts. In this application, verbose tracing is disabled, and the application flags conflicts with a warning instead.

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

Checking which levels of tracing are enabled can help you avoid potentially costly processing. In the example code, the application avoids additional processing if verbose tracing is already enabled. Conversely, the application might generate output only when a certain tracing level is enabled.

Security Considerations for Tracing

Trace files can include information about server and client computers, application data, and logins. (Passwords are not written to the trace file.) If verbose tracing is enabled, each changed row from the database is written to the trace file. Help protect the trace file by using the appropriate access control lists.

Complete Code Example

The following complete code example includes the code examples that are described earlier in this topic and additional code to perform synchronization. Before you run the application, perform the following steps:

  1. Create a project in Visual Studio.

  2. Add references to the Sync Framework DLLs and the Utility class that is available in Utility Class for Database Provider How-to Topics.

  3. Create a configuration file and copy the XML code from the example shown earlier in this topic.

Be aware of the calls to methods in the Utility class:

  • Utility.MakeFailingChangeOnNode(Utility.ConnStr_SqlSync_Client) This makes a change at the client that fails when it is applied at the server. The constraint violation and related application exception are automatically written to the trace file as warnings.

  • Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Client, "Customer") and Utility.MakeConflictingChangeOnNode(Utility.ConnStr_SqlSync_Server, "Customer") These 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.

After you run the application, open the trace output file to see the messages that are written automatically; and the conflict warnings, which are the result of application code.

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
Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.