Condividi tramite


Procedura: analizzare il processo di sincronizzazione

In questo argomento viene illustrato come utilizzare l'infrastruttura di analisi e la classe di analisi in Sync Framework. Gli esempi contenuti in questo argomento riguardano i tipi e gli eventi di Sync Framework seguenti:

Per informazioni su come eseguire il codice di esempio, vedere "Applicazioni di esempio nelle procedure" in Programmazione di attività comuni di sincronizzazione client e server.

Informazioni sull'analisi in Synchronization Services

L'analisi comporta la registrazione di dati, metadati e operazioni delle applicazioni e il trasferimento di queste informazioni a un listener. Un listener scrive con frequenza le informazioni di analisi in un file, ma è in grado di gestirle anche in in altri modi. Sync Framework include funzionalità di analisi per i provider di sincronizzazione client e server. Nelle applicazioni distribuite l'analisi può essere molto importante perché consente di risolvere i problemi che potrebbero altrimenti essere difficili da individuare.

In Sync Framework l'analisi include i componenti seguenti:

  • Infrastruttura di analisi basata sull'implementazione dell'analisi in .NET Framework, in particolare la classe TraceListener. Vengono analizzate le operazioni dei provider client e server più importanti e i metadati principali vengono forniti a uno o più listener.

  • Oggetto SyncTracer. Questo oggetto consente di determinare il livello di analisi attivato e di scrivere messaggi nell'output di analisi in base agli eventi dell'applicazione.

Oltre ai componenti di analisi forniti da Sync Framework, la risoluzione dei problemi richiede in genere l'utilizzo di altri strumenti, ad esempio un debugger o SQL Server Profiler. L'output di analisi può ad esempio includere informazioni su un database di SQL Server, quindi è necessario utilizzare SQL Server Profiler per ottenere dati più dettagliati sull'attività del database generata dal provider di sincronizzazione server.

Utilizzo dell'infrastruttura di analisi

Per impostazione predefinita, l'analisi non è attivata per le applicazioni Sync Framework. Per configurare un listener di analisi, modificare il file app.config per l'applicazione. Per ulteriori informazioni su questo file, vedere la documentazione di Visual Studio. In questo file è possibile aggiungere un listener, impostarne il tipo e i parametri correlati, rimuovere un listener o cancellare tutti i listener impostati in precedenza dall'applicazione. Per ulteriori informazioni, vedere la documentazione di .NET Framework relativa all'analisi. Il file di configurazione deve essere simile a quello dell'esempio seguente.

<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="c:\TraceSample.txt"/>
      </listeners>
    </trace>
  </system.diagnostics>
</configuration>

I nodi XML seguenti sono inclusi nel codice di esempio:

  • Un listener di tipo System.Diagnostics.TextWriterTraceListener (TextWriterTraceListener), con il percorso c:\TraceSample.txt.

  • Un'opzione denominata SyncTracer con valore pari a 3. Nella tabella seguente vengono illustrati tutti i valori e viene indicata la loro correlazione con l'output di analisi.

    Valore dell'opzione Livello di analisi Output

    0

    off

    Nessun messaggio ai listener di analisi.

    1

    error

    Solo messaggi di errore ai listener di analisi.

    2

    warning

    Messaggi di errore e di avviso ai listener di analisi.

    3

    info

    Messaggi informativi, di avviso e di errore ai listener di analisi.

    4

    verbose

    Tutti i messaggi ai listener di analisi.

    L'analisi comporta un sovraccarico, pertanto è necessario trovare un equilibrio tra l'analisi e i requisiti dell'applicazione in fatto di prestazioni. Nella maggior parte dei casi le impostazioni info e verbose risultano appropriate solo durante le fasi di sviluppo e di risoluzione dei problemi delle applicazioni.

Nel file di configurazione seguente viene illustrato un esempio per dispositivi.

<configuration>

   <traceSettings>

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

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

   </traceSettings>

</configuration>

Il file deve essere denominato trace.config.txt e posizionato nella directory dell'applicazione nel dispositivo. Se il file viene inserito in una soluzione Visual Studio, sarà possibile distribuirlo con l'applicazione.

Il segmento di file seguente deriva da un'analisi configurata utilizzando un valore di SyncTracer pari a 3. Ogni riga inizia con il tipo di output. In questo caso, l'intero output è di tipo INFO. Se si verificasse un errore, le righe pertinenti inizierebbero con ERROR.

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

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

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

INFO, MyApp.vshost, 10, 03/20/2008 17:43:03:027, Mapped Originator Id: 0

INFO, MyApp.vshost, 10, 03/20/2008 17:43:03:042,

INFO, MyApp.vshost, 10, 03/20/2008 17:43:03:042, ----- Enumerating Inserts for Table Customer -----

INFO, MyApp.vshost, 10, 03/20/2008 17:43:03:058, Changes Enumerated: 5

INFO, MyApp.vshost, 10, 03/20/2008 17:43:03:058, --- End Enumerating Inserts for Table Customer ---

Utilizzo dell'oggetto SyncTracer

L'oggetto SyncTracer consente di scrivere dati di analisi specifici dell'applicazione nel file di analisi. Questi dati possono fornire informazioni contestuali sulle operazioni eseguite da un'applicazione in un momento specifico. Questo oggetto consente di eseguire le attività seguenti:

  • Determinare il livello di analisi attivato, tramite i metodi seguenti:

  • Scrivere i messaggi nell'output di analisi in base agli eventi dell'applicazione, utilizzando i metodi seguenti e altri overload per ogni metodo:

    Per restituire un messaggio informativo, è ad esempio possibile utilizzare il codice seguente:

    SyncTracer.Info("Informational message")

    Se è attivato il livello Info, il messaggio viene scritto nell'output; in caso contrario, viene ignorato.

    Possono essere creati anche messaggi più complessi, come il seguente. I numeri specificati consentono di impostare il livello di rientro nel file di output.

    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)

    L'output è il seguente:

    Processing table t1

    Applying Deletes

    7 Rows Deleted

    Applying Inserts

    9 Rows inserted

L'esempio di codice seguente consente di scrivere nella console informazioni relative ai livelli di analisi attivati. Il file di configurazione specifica un valore pari a 3 per l'opzione SyncTracer che corrisponde a Info. Error, Warning e Info restituiscono pertanto True, mentre Verbose restituisce 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())

Nell'esempio di codice seguente viene illustrato come scrivere messaggi di avviso formattati relativi ai conflitti di dati nell'output di analisi. Per ulteriori informazioni sui conflitti, vedere Procedura: gestire conflitti di dati ed errori. L'analisi dettagliata include le informazioni sui conflitti. In questa applicazione l'analisi dettagliata è disabilitata e l'applicazione contrassegna invece i conflitti con un avviso.

if (SyncTracer.IsVerboseEnabled() == false && e.Conflict.ConflictType != ConflictType.ErrorsOccurred)
{
    DataTable conflictingServerChange = e.Conflict.ServerChange;
    DataTable conflictingClientChange = e.Conflict.ClientChange;
    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 CLIENT {0}", e.Session.ClientId);
    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.ConflictType <> ConflictType.ErrorsOccurred Then
    Dim conflictingServerChange As DataTable = e.Conflict.ServerChange
    Dim conflictingClientChange As DataTable = e.Conflict.ClientChange
    Dim serverColumnCount As Integer = conflictingServerChange.Columns.Count
    Dim clientColumnCount As Integer = conflictingClientChange.Columns.Count
    Dim clientRowAsString As New StringBuilder()
    Dim serverRowAsString As New StringBuilder()

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

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

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

La verifica dei livelli di analisi attivati può consentire di evitare operazioni di elaborazione potenzialmente costose. Nel codice di esempio vengono evitate, nell'applicazione, operazioni di elaborazione aggiuntive se l'analisi dettagliata è già attivata. Viceversa, è possibile che l'applicazione generi output solo quando è attivato un determinato livello di analisi.

Considerazioni sulla sicurezza per l'analisi

I file di analisi possono includere informazioni su computer server e client, dati delle applicazioni e account di accesso. Le password non vengono scritte nel file di analisi. Se l'analisi dettagliata è attivata, ogni riga modificata nel database viene scritta nel file di analisi. Proteggere il file di analisi utilizzando elenchi di controllo di accesso appropriati.

Esempio di codice completo

L'esempio di codice completo seguente include gli esempi di codice descritti in precedenza in questo argomento e il codice aggiuntivo per eseguire la sincronizzazione. Prima di eseguire l'applicazione, eseguire i passaggi seguenti:

  1. Creare un progetto in Visual Studio.

  2. Aggiungere i riferimenti alle DLL di Sync Framework e alla classe Utility disponibile in Classe di utilità per le procedure relative al provider di database.

  3. Creare un file di configurazione e copiare il codice XML dall'esempio illustrato in precedenza in questo argomento.

Prestare attenzione alle due chiamate ai metodi nella classe Utility:

  • util.MakeFailingChangesOnClient() Questa chiamata comporta l'esecuzione di una modifica nel client che non riesce quando viene applicata nel server. La violazione del vincolo e l'eccezione dell'applicazione correlata vengono scritte automaticamente nel file di analisi come avvisi.

  • util.MakeConflictingChangesOnClientAndServer() Questa chiamata comporta l'esecuzione di modifiche nel client e nel server che provocano un conflitto quando viene eseguita la sincronizzazione. I conflitti vengono scritti nel file di analisi nel gestore eventi SampleServerSyncProvider_ApplyChangeFailed.

Dopo aver eseguito l'applicazione, aprire il file di output dell'analisi per vedere i messaggi scritti automaticamente e gli avvisi relativi ai conflitti generati dal codice dell'applicazione.

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.Server;
using Microsoft.Synchronization.Data.SqlServerCe;

namespace Microsoft.Samples.Synchronization
{
    class Program
    {
        static void Main(string[] args)
        {

            //The SampleStats class handles information from the SyncStatistics
            //object that the Synchronize method returns.
            SampleStats sampleStats = new SampleStats();

            //Delete and re-create the database. The client synchronization
            //provider also enables you to create the client database 
            //if it does not exist.
            Utility.SetPassword_SqlCeClientSync();
            Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeClientSync, true);

            //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());

            //Initial synchronization. Instantiate the SyncAgent
            //and call Synchronize.
            SampleSyncAgent sampleSyncAgent = new SampleSyncAgent();
            SyncStatistics syncStatistics = sampleSyncAgent.Synchronize();
            sampleStats.DisplayStats(syncStatistics, "initial");

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

            //Make changes at the client and server that conflict
            //when they are synchronized. The conflicts are written
            //to the trace file in the SampleServerSyncProvider_ApplyChangeFailed
            //event handler.
            Utility.MakeConflictingChangesOnClientAndServer();

            //Subsequent synchronization.
            syncStatistics = sampleSyncAgent.Synchronize();
            sampleStats.DisplayStats(syncStatistics, "subsequent");

            //Return server data back to its original state.
            Utility.CleanUpServer();

            //Exit.
            Console.Write("\nPress Enter to close the window.");
            Console.ReadLine();
        }
    }

    //Create a class that is derived from 
    //Microsoft.Synchronization.SyncAgent.
    public class SampleSyncAgent : SyncAgent
    {
        public SampleSyncAgent()
        {
            //Instantiate a client synchronization provider and specify it
            //as the local provider for this synchronization agent.
            this.LocalProvider = new SampleClientSyncProvider();

            //Instantiate a server synchronization provider and specify it
            //as the remote provider for this synchronization agent.
            this.RemoteProvider = new SampleServerSyncProvider();

            //Add the Customer table: specify a synchronization direction of
            //DownloadOnly.
            SyncTable customerSyncTable = new SyncTable("Customer");
            customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            customerSyncTable.SyncDirection = SyncDirection.Bidirectional;
            this.Configuration.SyncTables.Add(customerSyncTable);
        }
    }

    //Create a class that is derived from 
    //Microsoft.Synchronization.Server.DbServerSyncProvider.
    public class SampleServerSyncProvider : DbServerSyncProvider
    {
        public SampleServerSyncProvider()
        {
            //Create a connection to the sample server database.
            Utility util = new Utility();
            SqlConnection serverConn = new SqlConnection(Utility.ConnStr_DbServerSync);
            this.Connection = serverConn;

            //Create a command to retrieve a new anchor value from
            //the server. In this case, we use a timestamp value
            //that is retrieved and stored in the client database.
            //During each synchronization, the new anchor value and
            //the last anchor value from the previous synchronization
            //are used: the set of changes between these upper and
            //lower bounds is synchronized.
            //
            //SyncSession.SyncNewReceivedAnchor is a string constant; 
            //you could also use @sync_new_received_anchor directly in 
            //your queries.
            SqlCommand selectNewAnchorCommand = new SqlCommand();
            string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
            selectNewAnchorCommand.CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1";
            selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
            selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Connection = serverConn;
            this.SelectNewAnchorCommand = selectNewAnchorCommand;


            //Create a SyncAdapter for the Customer table by using 
            //the SqlSyncAdapterBuilder:
            //  * Specify the base table and tombstone table names.
            //  * Specify the columns that are used to track when
            //    and where changes are made.
            //  * Specify bidirectional synchronization.
            //  * Call ToSyncAdapter to create the SyncAdapter.
            //  * Specify a name for the SyncAdapter that matches the
            //    the name specified for the corresponding SyncTable.
            //    Do not include the schema names (Sales in this case).

            SqlSyncAdapterBuilder customerBuilder = new SqlSyncAdapterBuilder(serverConn);

            customerBuilder.TableName = "Sales.Customer";
            customerBuilder.TombstoneTableName = customerBuilder.TableName + "_Tombstone";
            customerBuilder.SyncDirection = SyncDirection.Bidirectional;
            customerBuilder.CreationTrackingColumn = "InsertTimestamp";
            customerBuilder.UpdateTrackingColumn = "UpdateTimestamp";
            customerBuilder.DeletionTrackingColumn = "DeleteTimestamp";
            customerBuilder.CreationOriginatorIdColumn = "InsertId";
            customerBuilder.UpdateOriginatorIdColumn = "UpdateId";
            customerBuilder.DeletionOriginatorIdColumn = "DeleteId";

            SyncAdapter customerSyncAdapter = customerBuilder.ToSyncAdapter();
            customerSyncAdapter.TableName = "Customer";
            this.SyncAdapters.Add(customerSyncAdapter);

            //Handle the ApplyChangeFailed event. This allows us to write  
            //information to the trace file about any conflicts that occur.
            this.ApplyChangeFailed += new EventHandler<ApplyChangeFailedEventArgs>(SampleServerSyncProvider_ApplyChangeFailed);
        }

        private void SampleServerSyncProvider_ApplyChangeFailed(object sender, ApplyChangeFailedEventArgs 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.ConflictType != ConflictType.ErrorsOccurred)
            {
                DataTable conflictingServerChange = e.Conflict.ServerChange;
                DataTable conflictingClientChange = e.Conflict.ClientChange;
                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 CLIENT {0}", e.Session.ClientId);
                SyncTracer.Warning(2, "** Client change **");
                SyncTracer.Warning(2, clientRowAsString.ToString());
                SyncTracer.Warning(2, "** Server change **");
                SyncTracer.Warning(2, serverRowAsString.ToString());
            }
        }
    }

    //Create a class that is derived from 
    //Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider.
    //You can just instantiate the provider directly and associate it
    //with the SyncAgent, but you could use this class to handle client 
    //provider events and other client-side processing.
    public class SampleClientSyncProvider : SqlCeClientSyncProvider
    {

        public SampleClientSyncProvider()
        {
            //Specify a connection string for the sample client database.
            Utility util = new Utility();
            this.ConnectionString = Utility.ConnStr_SqlCeClientSync;
            
            this.SchemaCreated += new EventHandler<SchemaCreatedEventArgs>(SampleClientSyncProvider_SchemaCreated);            
        }

        private void SampleClientSyncProvider_SchemaCreated(object sender, SchemaCreatedEventArgs e)
        {
            string tableName = e.Table.TableName;
            Utility util = new Utility();

            //Call ALTER TABLE on the client. This must be done
            //over the same connection and within the same
            //transaction that Sync Framework uses
            //to create the schema on the client.
            Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "Customer");

        }
    }

    //Handle the statistics that are returned by the SyncAgent.
    public class SampleStats
    {
        public void DisplayStats(SyncStatistics 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 Downloaded: " + syncStatistics.TotalChangesDownloaded);
            Console.WriteLine("Total Changes Uploaded: " + syncStatistics.TotalChangesUploaded);
            Console.WriteLine("Complete Time: " + syncStatistics.SyncCompleteTime);
            Console.WriteLine(String.Empty);

        }
    }
}
Imports System
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.Server
Imports Microsoft.Synchronization.Data.SqlServerCe


Class Program

    Shared Sub Main(ByVal args() As String)

        'The SampleStats class handles information from the SyncStatistics
        'object that the Synchronize method returns.
        Dim sampleStats As New SampleStats()

        'Delete and re-create the database. The client synchronization
        'provider also enables you to create the client database 
        'if it does not exist.
        Utility.SetPassword_SqlCeClientSync()
        Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeClientSync, True)

        '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())

        'Initial synchronization. Instantiate the SyncAgent
        'and call Synchronize.
        Dim sampleSyncAgent As New SampleSyncAgent()
        Dim syncStatistics As SyncStatistics = sampleSyncAgent.Synchronize()
        sampleStats.DisplayStats(syncStatistics, "initial")

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

        'Make changes at the client and server that conflict
        'when they are synchronized. The conflicts are written
        'to the trace file in the SampleServerSyncProvider_ApplyChangeFailed
        'event handler.
        Utility.MakeConflictingChangesOnClientAndServer()

        'Subsequent synchronization.
        syncStatistics = sampleSyncAgent.Synchronize()
        sampleStats.DisplayStats(syncStatistics, "subsequent")

        'Return server data back to its original state.
        Utility.CleanUpServer()

        'Exit.
        Console.Write(vbLf + "Press Enter to close the window.")
        Console.ReadLine()

    End Sub 'Main
End Class 'Program

'Create a class that is derived from 
'Microsoft.Synchronization.SyncAgent.
Public Class SampleSyncAgent
    Inherits SyncAgent

    Public Sub New()
        'Instantiate a client synchronization provider and specify it
        'as the local provider for this synchronization agent.
        Me.LocalProvider = New SampleClientSyncProvider()

        'Instantiate a server synchronization provider and specify it
        'as the remote provider for this synchronization agent.
        Me.RemoteProvider = New SampleServerSyncProvider()

        'Add the Customer table: specify a synchronization direction of
        'DownloadOnly.
        Dim customerSyncTable As New SyncTable("Customer")
        customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
        customerSyncTable.SyncDirection = SyncDirection.Bidirectional
        Me.Configuration.SyncTables.Add(customerSyncTable)

    End Sub 'New 
End Class 'SampleSyncAgent

'Create a class that is derived from 
'Microsoft.Synchronization.Server.DbServerSyncProvider.
Public Class SampleServerSyncProvider
    Inherits DbServerSyncProvider

    Public Sub New()

        'Create a connection to the sample server database.
        Dim util As New Utility()
        Dim serverConn As New SqlConnection(Utility.ConnStr_DbServerSync)
        Me.Connection = serverConn

        'Create a command to retrieve a new anchor value from
        'the server. In this case, we use a timestamp value
        'that is retrieved and stored in the client database.
        'During each synchronization, the new anchor value and
        'the last anchor value from the previous synchronization
        'are used: the set of changes between these upper and
        'lower bounds is synchronized.
        '
        'SyncSession.SyncNewReceivedAnchor is a string constant; 
        'you could also use @sync_new_received_anchor directly in 
        'your queries.
        Dim selectNewAnchorCommand As New SqlCommand()
        Dim newAnchorVariable As String = "@" + SyncSession.SyncNewReceivedAnchor
        selectNewAnchorCommand.CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1"
        selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp)
        selectNewAnchorCommand.Parameters(newAnchorVariable).Direction = ParameterDirection.Output
        selectNewAnchorCommand.Connection = serverConn
        Me.SelectNewAnchorCommand = selectNewAnchorCommand

        'Create a SyncAdapter for the Customer table by using 
        'the SqlSyncAdapterBuilder:
        '  * Specify the base table and tombstone table names.
        '  * Specify the columns that are used to track when
        '    and where changes are made.
        '  * Specify bidirectional synchronization.
        '  * Call ToSyncAdapter to create the SyncAdapter.
        '  * Specify a name for the SyncAdapter that matches the
        '    the name specified for the corresponding SyncTable.
        '    Do not include the schema names (Sales in this case).
        Dim customerBuilder As New SqlSyncAdapterBuilder(serverConn)

        customerBuilder.TableName = "Sales.Customer"
        customerBuilder.TombstoneTableName = customerBuilder.TableName + "_Tombstone"
        customerBuilder.SyncDirection = SyncDirection.Bidirectional
        customerBuilder.CreationTrackingColumn = "InsertTimestamp"
        customerBuilder.UpdateTrackingColumn = "UpdateTimestamp"
        customerBuilder.DeletionTrackingColumn = "DeleteTimestamp"
        customerBuilder.CreationOriginatorIdColumn = "InsertId"
        customerBuilder.UpdateOriginatorIdColumn = "UpdateId"
        customerBuilder.DeletionOriginatorIdColumn = "DeleteId"

        Dim customerSyncAdapter As SyncAdapter = customerBuilder.ToSyncAdapter()
        customerSyncAdapter.TableName = "Customer"
        Me.SyncAdapters.Add(customerSyncAdapter)

        'Handle the ApplyChangeFailed event. This allows us to write  
        'information to the trace file about any conflicts that occur.
        AddHandler Me.ApplyChangeFailed, AddressOf SampleServerSyncProvider_ApplyChangeFailed

     End Sub 'New


    Private Sub SampleServerSyncProvider_ApplyChangeFailed(ByVal sender As Object, ByVal e As ApplyChangeFailedEventArgs)

        '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.ConflictType <> ConflictType.ErrorsOccurred Then
            Dim conflictingServerChange As DataTable = e.Conflict.ServerChange
            Dim conflictingClientChange As DataTable = e.Conflict.ClientChange
            Dim serverColumnCount As Integer = conflictingServerChange.Columns.Count
            Dim clientColumnCount As Integer = conflictingClientChange.Columns.Count
            Dim clientRowAsString As New StringBuilder()
            Dim serverRowAsString As New StringBuilder()

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

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

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

    End Sub 'SampleServerSyncProvider_ApplyChangeFailed 
End Class 'SampleServerSyncProvider

'Create a class that is derived from 
'Microsoft.Synchronization.Data.SqlServerCe.SqlCeClientSyncProvider.
'You can just instantiate the provider directly and associate it
'with the SyncAgent, but you could use this class to handle client 
'provider events and other client-side processing.
Public Class SampleClientSyncProvider
    Inherits SqlCeClientSyncProvider


    Public Sub New()

        'Specify a connection string for the sample client database.
        Dim util As New Utility()
        Me.ConnectionString = Utility.ConnStr_SqlCeClientSync

    End Sub 'New


    Private Sub SampleClientSyncProvider_SchemaCreated(ByVal sender As Object, ByVal e As SchemaCreatedEventArgs)
        Dim tableName As String = e.Table.TableName
        Dim util As New Utility()

        'Call ALTER TABLE on the client. This must be done
        'over the same connection and within the same
        'transaction that Sync Framework uses
        'to create the schema on the client.
        Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "Customer")

    End Sub 'SampleClientSyncProvider_SchemaCreated 

End Class 'SampleClientSyncProvider

'Handle the statistics that are returned by the SyncAgent.
Public Class SampleStats

    Public Sub DisplayStats(ByVal syncStatistics As SyncStatistics, 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: " & syncStatistics.SyncStartTime)
        Console.WriteLine("Total Changes Downloaded: " & syncStatistics.TotalChangesDownloaded)
        Console.WriteLine("Total Changes Uploaded: " & syncStatistics.TotalChangesUploaded)
        Console.WriteLine("Complete Time: " & syncStatistics.SyncCompleteTime)
        Console.WriteLine(String.Empty)

    End Sub 'DisplayStats 
End Class 'SampleStats

Vedere anche

Concetti

Programmazione di attività comuni di sincronizzazione client e server