Freigeben über


Vorgehensweise: Ablaufverfolgung des Synchronisierungsvorgangs

In diesem Thema wird die Verwendung der Ablaufverfolgungsinfrastruktur und der Ablaufverfolgungsklasse in Sync Framework dargestellt. In den Beispielen in diesem Thema liegt der Schwerpunkt auf den folgenden Sync Framework-Typen und -Ereignissen:

Informationen über die Ausführung von Beispielcode finden Sie unter "Beispielanwendungen in den Themen zur Vorgehensweise" in Programmieren von allgemeinen Client- und Serversynchronisierungsaufgaben.

Die Ablaufverfolgung von Synchronization Services

Die Ablaufverfolgung umfasst das Aufzeichnen von Anwendungsvorgängen, Daten und Metadaten sowie das Bereitstellen dieser Informationen für einen Listener. Ein Listener schreibt in regelmäßigen Abständen Ablaufverfolgungsinformationen in eine Datei, kann diese Informationen jedoch auch anderweitig verwerten. Sync Framework enthält die Ablaufverfolgung für die Client- und Serversynchronisierungsanbieter. In verteilten Anwendungen kann die Ablaufverfolgung sehr wichtig sein, da sie die Behebung von Problemen ermöglicht, die sonst möglicherweise schwierig zu erkennen sind.

Die Ablaufverfolgung in Sync Framework besteht aus den folgenden Komponenten:

  • Einer Ablaufverfolgungsinfrastruktur, die auf der .NET Framework-Implementierung der Ablaufverfolgung basiert, insbesondere der TraceListener-Klasse. Die wichtigsten Vorgänge der Client- und Serveranbieter werden überwacht, und Schlüsselmetadaten werden für einen oder mehrere Listener bereitgestellt.

  • Dem SyncTracer-Objekt. Damit können Sie festlegen, welche Ablaufverfolgungsebene aktiviert ist, und auf der Grundlage von Anwendungsereignissen Meldungen in die Ablaufverfolgungsausgabe schreiben.

Zusätzlich zu den von Sync Framework bereitgestellten Ablaufverfolgungskomponenten werden für die Problembehandlung meist noch weitere Tools verwendet, wie ein Debugger oder SQL Server Profiler. Die Ablaufverfolgungsausgabe kann beispielsweise Informationen über eine SQL Server-Datenbank enthalten. Sie können in diesem Fall SQL Server Profiler verwenden, um ausführlichere Informationen zu den Datenbankaktivitäten zu erhalten, die vom Synchronisierungsanbieter generiert wurden.

Verwenden der Ablaufverfolgungsinfrastruktur

Die Ablaufverfolgung ist in der Standardeinstellung für Sync Framework-Anwendungen nicht aktiviert. Bearbeiten Sie die Datei app.config für Ihre Anwendung, um einen Ablaufverfolgungslistener zu konfigurieren. Weitere Informationen über diese Datei finden Sie in der Visual Studio-Dokumentation. Sie können in dieser Datei einen Listener hinzufügen, dessen Typ und verknüpfte Parameter festlegen, einen Listener entfernen oder alle Listener löschen, die vorher von der Anwendung festgelegt wurden. Weitere Informationen finden Sie in der .NET Framework-Dokumentation über die Ablaufverfolgung. Die Konfigurationsdatei sollte dem folgenden Beispiel ähneln.

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

Der Beispielcode enthält die folgenden XML-Knoten:

  • Einen Listener vom Typ System.Diagnostics.TextWriterTraceListener (TextWriterTraceListener) mit dem Pfad für c:\TraceSample.txt.

  • Einen Schalter mit dem Namen SyncTracer. Dieser hat den Wert 3. In der folgenden Tabelle sind alle Werte und deren Bedeutung für die Ablaufverfolgungsausgabe dargestellt.

    Schalterwert Ablaufverfolgungsebene Ausgabe

    0

    off

    Keine Meldungen an Ablaufverfolgungslistener.

    1

    error

    Nur Fehlermeldungen an Ablaufverfolgungslistener.

    2

    warning

    Fehler- und Warnmeldungen an Ablaufverfolgungslistener.

    3

    info

    Informations-, Fehler- und Warnmeldungen an Ablaufverfolgungslistener.

    4

    verbose

    Alle Meldungen an Ablaufverfolgungslistener.

    Die Ablaufverfolgung verursacht einen höheren Leistungsaufwand. Sie sollten die Ablaufverfolgung daher mit den Leistungsanforderungen Ihrer Anwendung abstimmen. In den meisten Fällen sind die Einstellungen info und verbose nur während der Entwicklung und der Problembehandlung einer Anwendung angemessen.

Die folgende Konfigurationsdatei zeigt ein Beispiel für Geräte.

<configuration>

   <traceSettings>

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

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

   </traceSettings>

</configuration>

Die Datei sollte den Namen trace.config.txt erhalten und auf dem Gerät im Anwendungsverzeichnis gespeichert werden. Wenn Sie die Datei in eine Visual Studio-Lösung einbinden, kann sie zusammen mit der Anwendung bereitgestellt werden.

Das folgende Dateisegment stammt von einer Ablaufverfolgung, die mit dem SyncTracer-Wert 3 konfiguriert wurde. Jede Zeile beginnt mit der Ausgabeart. In diesem Fall handelt es sich bei der gesamten Ausgabe um INFO. Wenn ein Fehler auftritt, beginnen die entsprechenden Zeilen mit 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 ---

Verwenden des 'SyncTracer'-Objekts

Das SyncTracer-Objekt ermöglicht das Schreiben anwendungsspezifischer Ablaufverfolgungsdaten in die Ablaufverfolgungsdatei. Damit können Kontextinformationen über die Aktionen einer Datei zu einem bestimmten Zeitpunkt bereitgestellt werden. Dieses Objekt ermöglicht das Ausführen folgender Aufgaben:

  • Ermitteln der aktivierten Ablaufverfolgungsebene mithilfe der folgenden Methoden:

  • Schreiben von Meldungen in die Ausgabe der Ablaufverfolgung. Dies geschieht beruhend auf Anwendungsereignissen und unter Verwendung der folgenden Methoden und deren Überladungen:

    Um beispielsweise eine Informationsmeldung in die Ausgabe zu schreiben, kann folgender Code verwendet werden:

    SyncTracer.Info("Informational message")

    Wenn die Ebene "Info" aktiviert ist, wird die Meldung in die Ausgabe geschrieben. Andernfalls wird sie ignoriert.

    Komplexere Meldungen wie die folgende können ebenfalls formuliert werden. Mit den angegebenen Zahlen wird die Einrückungsebene in der Ausgabedatei festgelegt.

    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)

    Die Ausgabe lautet wie folgt:

    Processing table t1

    Applying Deletes

    7 Rows Deleted

    Applying Inserts

    9 Rows inserted

Mit dem folgenden Codebeispiel werden Informationen über die aktivierten Ablaufverfolgungsebenen in die Konsole geschrieben. In der Konfigurationsdatei ist der Wert 3 für den SyncTracer-Schalter festgelegt. Dies entspricht Info. Daher geben Error, Warning und Info den Wert True und Verbose den Wert False zurück.

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

Im folgenden Codebeispiel wird dargestellt, wie formatierte Warnmeldungen über Datenkonflikte in die Ablaufverfolgungsausgabe geschrieben werden. Weitere Informationen über Konflikte finden Sie unter Vorgehensweise: Behandeln von Datenkonflikten und Fehlern. Bei der ausführlichen Ablaufverfolgung werden Informationen über Konflikte ausgegeben. In dieser Anwendung ist die ausführliche Ablaufverfolgung deaktiviert, und die Anwendung kennzeichnet Konflikte stattdessen mit einer Warnung.

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

Aufwändige Bearbeitung kann vermieden werden, indem überprüft wird, welche Ablaufverfolgungsebenen aktiviert sind. Im Beispielcode vermeidet die Anwendung zusätzliche Bearbeitung, wenn die ausführliche Ablaufverfolgung bereits aktiviert ist. Umgekehrt erstellt die Anwendung möglicherweise nur dann eine Ausgabe, wenn eine bestimmte Ablaufverfolgungsebene aktiviert ist.

Sicherheitsüberlegungen bei der Ablaufverfolgung

Ablaufverfolgungsdateien können Informationen über Server- und Clientcomputer, Anwendungsdaten und Anmeldedaten enthalten. (Kennwörter werden nicht in die Ablaufverfolgungsdatei geschrieben.) Wenn die ausführliche Ablaufverfolgung aktiviert ist, wird jede in der Datenbank geänderte Zeile in die Ablaufverfolgungsdatei geschrieben. Sie können die Ablaufverfolgungsdatei schützen, indem Sie die entsprechenden Zugriffssteuerungslisten verwenden.

Vollständiges Codebeispiel

Das folgende vollständige Codebeispiel enthält die Codebeispiele, die weiter oben in diesem Thema beschrieben wurden, sowie zusätzlichen Code zum Ausführen der Synchronisierung. Führen Sie folgende Schritte aus, bevor Sie die Anwendung ausführen:

  1. Erstellen Sie ein Projekt in Visual Studio.

  2. Fügen Sie Verweise auf die Sync Framework-DLLs und die in 'Utility'-Klasse für Datenbankanbieter - Themen zur Vorgehensweise verfügbare Utility-Klasse hinzu.

  3. Erstellen Sie eine Konfigurationsdatei, und kopieren Sie den XML-Code aus dem oben in diesem Thema dargestellten Beispiel.

Achten Sie auf die beiden Methodenaufrufe in der Utility-Klasse:

  • util.MakeFailingChangesOnClient() Damit wird eine Änderung am Client vorgenommen, für die das Übernehmen für den Server fehlschlägt. Die Einschränkungsverletzung und die damit verbundene Anwendungsausnahme werden automatisch als Warnungen in die Ablaufverfolgungsdatei geschrieben.

  • util.MakeConflictingChangesOnClientAndServer() Damit werden Änderungen an Client und Server vorgenommen, die beim Synchronisieren einen Konflikt verursachen. Die Konflikte werden im SampleServerSyncProvider_ApplyChangeFailed-Ereignishandler in die Ablaufverfolgungsdatei geschrieben.

Öffnen Sie nach dem Ausführen der Anwendung die Ausgabedatei der Ablaufverfolgung, um die automatisch geschriebenen Meldungen und die Konfliktwarnungen zu sehen, die der Anwendungscode zur Folge hat.

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

Siehe auch

Konzepte

Programmieren von allgemeinen Client- und Serversynchronisierungsaufgaben