다음을 통해 공유


방법: 동기화 프로세스 추적

이 항목에서는 Sync Framework의 추적 인프라와 추적 클래스를 사용하는 방법을 보여 줍니다. 이 항목의 예제에서는 다음과 같은 Sync Framework 형식 및 이벤트를 중점적으로 설명합니다.

샘플 코드를 실행하는 방법에 대한 자세한 내용은 일반적인 클라이언트 및 서버 동기화 태스크 프로그래밍의 "방법 항목의 예제 응용 프로그램"을 참조하십시오.

Synchronization Services의 추적 이해

추적에는 응용 프로그램 작업, 데이터 및 메타데이터를 기록하고 이 정보를 수신기에 제공하는 작업이 포함됩니다. 리스너는 파일에 추적 정보를 기록하는 경우가 많지만 다른 방식으로 정보를 처리할 수도 있습니다. Sync Framework에는 클라이언트 및 서버 동기화 공급자용 추적 기능이 포함되어 있습니다. 분산 응용 프로그램에서 추적은 다른 방법으로는 발견하기 어려운 문제를 해결할 수 있는 매우 중요한 기능입니다.

Sync Framework의 추적은 다음과 같은 요소로 구성되어 있습니다.

  • 추적의 .NET Framework 구현을 기반으로 하는 추적 인프라(특히 TraceListener 클래스). 클라이언트와 서버 공급자의 가장 중요한 작업이 추적되고 주요 메타데이터가 하나 이상의 수신기에 제공됩니다.

  • SyncTracer 개체. 이 개체를 사용하면 활성화된 추적 수준을 확인하고 응용 프로그램 이벤트에 따라 추적 출력에 메시지를 기록할 수 있습니다.

일반적으로 문제 해결에는 Sync Framework에서 제공하는 추적 구성 요소 외에도 디버거나 SQL Server 프로파일러와 같은 다른 도구가 관련됩니다. 예를 들어, 추적 출력에는 SQL Server 데이터베이스에 대한 정보가 포함될 수 있으며, 이 경우 SQL Server 프로파일러를 사용하여 서버 동기화 공급자가 생성한 데이터베이스 작업에 대한 정보를 얻을 수 있습니다.

추적 인프라 사용

Sync Framework 응용 프로그램의 경우 기본적으로 추적이 활성화되어 있지 않습니다. 추적 수신기를 구성하려면 응용 프로그램의 app.config 파일을 편집해야 합니다. 이 파일에 대한 자세한 내용은 Visual Studio 설명서를 참조하십시오. 이 파일에서 수신기를 추가하거나, 수신기 유형 및 관련 매개 변수를 설정하거나, 수신기를 제거하거나, 응용 프로그램에서 이전에 설정된 수신기를 모두 지울 수 있습니다. 자세한 내용은 추적에 대한 .NET Framework 설명서를 참조하십시오. 구성 파일은 다음 예제와 유사합니다.

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

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

다음과 같은 XML 노드가 예제 코드에 포함되어 있습니다.

  • System.Diagnostics.TextWriterTraceListener(TextWriterTraceListener) 형식의 수신기(경로: c:\TraceSample.txt)

  • SyncTracer라는 스위치. 이 스위치의 값은 3입니다. 다음 표에서는 모든 값을 제공하고 각 값과 추적 출력의 관계를 보여 줍니다.

    스위치 값 추적 수준 출력

    0

    off

    추적 수신기에 메시지가 제공되지 않습니다.

    1

    error

    추적 수신기에 오류 메시지만 제공됩니다.

    2

    warning

    추적 수신기에 오류 및 경고 메시지가 제공됩니다.

    3

    info

    추적 수신기에 알림, 경고 및 오류 메시지가 제공됩니다.

    4

    verbose

    추적 수신기에 모든 메시지가 제공됩니다.

    추적에는 오버헤드가 있으므로 응용 프로그램의 성능 요구 사항에 맞게 추적 수준을 조정해야 합니다. 대부분의 경우 infoverbose 설정은 응용 프로그램 개발과 문제 해결 중에만 적합합니다.

다음 구성 파일은 장치에 대한 예제를 보여줍니다.

<configuration>

   <traceSettings>

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

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

   </traceSettings>

</configuration>

파일의 이름을 trace.config.txt로 지정하여 장치의 응용 프로그램 디렉터리에 두어야 합니다. Visual Studio 솔루션에 파일을 포함하면 응용 프로그램을 사용하여 해당 파일을 배포할 수 있습니다.

다음 파일 세그먼트는 값이 3SyncTracer를 사용하여 구성된 추적의 일부입니다. 각 줄은 출력 유형으로 시작됩니다. 이 경우에는 모든 출력이 INFO입니다. 오류가 발생한 경우 관련 줄은 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 ---

SyncTracer 개체 사용

SyncTracer 개체를 사용하면 추적 파일에 응용 프로그램 관련 추적 데이터를 기록할 수 있습니다. 이에 따라 응용 프로그램에서 특정 시간에 수행하는 작업에 대한 컨텍스트 정보를 제공할 수 있습니다. 이 개체를 사용하여 다음 태스크를 수행할 수 있습니다.

  • 다음 메서드를 사용하여 활성화된 추적 수준을 확인합니다.

  • 다음 메서드와 각 메서드의 다른 오버로드를 사용하여 응용 프로그램 이벤트에 따라 추적 출력에 메시지를 기록합니다.

    예를 들어, 알림 메시지를 출력하려면 다음과 같은 코드를 사용할 수 있습니다.

    SyncTracer.Info("Informational message")

    Info 수준이 활성화되면 메시지가 출력에 기록되고, 그렇지 않으면 메시지가 무시됩니다.

    더 복잡한 메시지도 다음과 같이 구성할 수 있습니다. 지정된 숫자는 출력 파일에서 들여쓰기 수준을 설정합니다.

    SyncTracer.Verbose("Processing table t1")

    SyncTracer.Verbose(1, "Applying Deletes")

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

    SyncTracer.Verbose(1, "Applying Inserts")

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

    출력은 다음과 같습니다.

    Processing table t1

    Applying Deletes

    7 Rows Deleted

    Applying Inserts

    9 Rows inserted

다음 코드 예제에서는 활성화된 추적 수준에 대한 정보를 콘솔에 기록합니다. 구성 파일에서는 SyncTracer 스위치에 값 3을 지정합니다. 이 값은 Info에 해당하므로 Error, WarningInfoTrue를 반환하고, VerboseFalse를 반환합니다.

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

다음 코드 예제에서는 데이터 충돌에 대한 서식이 지정된 경고 메시지를 추적 출력에 기록하는 방법을 보여 줍니다. 충돌에 대한 자세한 내용은 방법: 데이터 충돌 및 오류 처리를 참조하십시오. 자세한 정보 표시 추적에는 충돌에 대한 정보가 포함됩니다. 이 응용 프로그램에서는 자세한 정보 표시 추적이 비활성화되어 있으며 응용 프로그램에서는 충돌을 경고로 표시합니다.

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

활성화된 추적 수준을 확인하면 비용이 많이 들 수 있는 처리를 방지할 수 있습니다. 예제 코드에서는 자세한 정보 표시 추적이 이미 활성화된 경우 응용 프로그램에서 추가 처리를 방지합니다. 이와 반대로, 특정 추적 수준이 활성화된 경우에만 응용 프로그램에서 출력을 생성할 수도 있습니다.

추적의 보안 고려 사항

추적 파일에는 서버 및 클라이언트 컴퓨터, 응용 프로그램 데이터, 로그인에 대한 정보가 포함될 수 있습니다. 암호는 추적 파일에 기록되지 않습니다. 자세한 정보 표시 추적이 활성화되어 있으면 데이터베이스에서 변경된 각 행이 추적 파일에 기록됩니다. 적절한 액세스 제어 목록을 사용하여 추적 파일을 보호해야 합니다.

전체 코드 예제

다음의 전체 코드 예제에는 이 항목 앞부분에서 설명한 코드 예제와 동기화를 수행하는 데 필요한 추가 코드가 포함되어 있습니다. 응용 프로그램을 실행하기 전에 다음 단계를 수행하십시오.

  1. Visual Studio에서 프로젝트를 만듭니다.

  2. 데이터베이스 공급자용 유틸리티 클래스 방법 항목에서 사용할 수 있는 Utility 클래스와 Sync Framework DLL에 대한 참조를 추가합니다.

  3. 구성 파일을 만들고 이 항목 앞부분에 나와 있는 예제에서 XML 코드를 복사합니다.

Utility 클래스에서 두 번의 메서드 호출에 유의해야 합니다.

  • util.MakeFailingChangesOnClient() 서버에서 적용될 때 실패하는 변경을 클라이언트에서 수행합니다. 제약 조건 위반과 관련 응용 프로그램 예외가 자동으로 추적 파일에 경고로 기록됩니다.

  • util.MakeConflictingChangesOnClientAndServer() 동기화될 때 충돌하는 변경을 클라이언트와 서버에서 수행합니다. 충돌은 SampleServerSyncProvider_ApplyChangeFailed 이벤트 처리기에서 추적 파일에 기록됩니다.

응용 프로그램을 실행한 후 추적 출력 파일을 열고 자동으로 기록된 메시지와 응용 프로그램 코드의 결과인 충돌 경고를 확인하십시오.

using System;
using System.IO;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlServerCe;
using Microsoft.Synchronization;
using Microsoft.Synchronization.Data;
using Microsoft.Synchronization.Data.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

참고 항목

개념

일반적인 클라이언트 및 서버 동기화 태스크 프로그래밍