다음을 통해 공유


방법: 공동 작업 동기화 구성 및 실행(SQL Server 이외)

참고

다른 ADO.NET 호환 데이터베이스 동기화 설명서 섹션의 항목에서는 Sync Framework를 사용하여 SQL Server 이외의 데이터베이스를 동기화하는 방법을 보여 줍니다. 이 릴리스에서는 SQL Server가 코드 예제에 사용되지만 표시되는 SQL Server 관련 개체(예: SqlConnection) 및 SQL 쿼리를 일부 수정하여 코드를 다른 ADO.NET 호환 데이터베이스에 대해 사용할 수 있습니다. SQL Server 동기화에 대한 자세한 내용은 방법: 공동 작업 동기화 구성 및 실행(SQL Server)를 참조하십시오.

이 항목에서는 Sync Framework를 사용하여 여러 데이터베이스를 동기화하는 응용 프로그램의 주요 부분을 살펴봅니다. 이 응용 프로그램의 코드에서는 다음과 같은 Sync Framework 클래스를 중점적으로 다룹니다.

샘플 코드를 실행하는 방법에 대한 자세한 내용은 다른 ADO.NET 호환 데이터베이스 동기화에서 "방법 항목의 예제 응용 프로그램"을 참조하십시오.

공동 작업 동기화에 대한 아키텍처 및 클래스에서 설명한 대로 동기화는 두 DbSyncProvider 인스턴스, 두 SqlCeSyncProvider 인스턴스 또는 두 종류의 인스턴스 사이에서 발생할 수 있습니다. 이 항목에 있는 예제 코드는 2 계층 응용 프로그램에서 사용되는 것이므로 N 계층 구성을 필요로 하는 두 SqlCeSyncProvider 인스턴스 동기화는 확인할 수 없습니다. N 계층 구성의 예는 Sync Framework SDK에 포함된 WebSharingAppDemo-CEProviderEndToEnd 샘플을 참조하십시오.

동기화를 구성하는 작업에는 다음과 같은 단계가 포함됩니다.

  1. 메타데이터를 저장할 추적 테이블, 각 서버 데이터베이스의 데이터 및 메타데이터를 업데이트하는 데 사용할 저장 프로시저 만들기. 자세한 내용은 방법: 공동 작업 동기화를 위한 서버 데이터베이스 프로비전(SQL Server 이외)을 참조하십시오.

  2. 스키마, 데이터 및 변경 내용 추적 인프라를 사용하여 각 서버 데이터베이스 초기화

동기화를 실행하는 작업에는 다음과 같은 단계가 포함됩니다.

  1. 서버 동기화 어댑터 및 동기화 공급자, 클라이언트 동기화 공급자 만들기

  2. 각 클라이언트 데이터베이스 초기화

  3. 동기화 조정자 만들기 및 노드 동기화

서버 데이터베이스 초기화

데이터베이스를 초기화할 때는 테이블 스키마와 변경 내용 추적 인프라 및 필요한 모든 초기 데이터를 각 데이터베이스로 복사합니다. DbSyncProvider를 사용하여 동기화되는 데이터베이스의 경우 Sync Framework는 테이블 스키마 또는 추적 인프라를 각 데이터베이스에 자동으로 만들지 않습니다. 응용 프로그램은 노드 동기화를 시도하기 전에 이러한 개체가 있는지 확인해야 합니다. 백업 및 복원이나 다른 기술을 사용하여 개체를 각 노드에 복사할 수 있지만 백업이 수행된 데이터베이스에서 변경이 발생하고 있지 않는 경우에만 복사해야 합니다. 첫 번째 데이터베이스가 사용 중이며 업데이트되고 있는 경우에는 스키마 및 변경 내용 추적 인프라만 각 노드로 복사하고 Sync Framework를 사용하여 데이터를 복사하는 것이 좋습니다. 다른 방법을 사용하여 데이터를 복사하면 첫 번째 노드에 커밋된 변경 내용이 다른 노드에서 손실될 수 있습니다. 최소한 하나의 노드에 데이터가 있으면 Sync Framework에서 각 노드의 데이터를 초기화할 수 있습니다. 이 항목의 예제 코드에서 사용하는 방식에서는 각 데이터베이스에 두 테이블이 들어 있지만 SyncSamplesDb_Peer1의 테이블에만 데이터가 들어 있습니다. 데이터는 첫 번째 동기화 세션 중에 다른 노드로 복사됩니다.

SqlCeSyncProvider의 초기화 옵션에 대한 자세한 내용은 이 항목 뒷부분에 나오는 클라이언트 데이터베이스 초기화를 참조하십시오.

동기화 실행

이 섹션의 코드 예제는 다음과 같은 범주로 나뉩니다.

  • 서버 데이터베이스에서 동기화될 각 테이블의 DbSyncAdapter 개체 및 명령

  • 서버의 DbSyncProvider 개체 및 명령

  • 클라이언트의 SqlCeSyncProvider 개체

  • 클라이언트 데이터베이스를 초기화하는 데 사용되는 SyncSchema 개체 및 GenerateSnapshot 메서드

  • 서버 및 클라이언트 데이터베이스를 동기화하는 데 사용되는 SyncOrchestrator 개체

동기화 어댑터

다음 속성은 각 테이블의 DbSyncAdapter 개체에서 설정됩니다.

동기화 어댑터 속성 용도

RowIdColumns

테이블의 기본 키 열을 지정합니다. 예를 들어 Customer 테이블에 기본 키 CustomerId가 있으면 코드는 adapter.RowIdColumns.Add(CustomerId)의 형태입니다.

SelectIncrementalChangesCommand

기본 테이블과 해당 변경 내용 추적 테이블을 조인하여 이전 동기화 세션 이후의 모든 변경 내용을 선택합니다. 이는 동기화할 수 있는 변경 내용의 최대 집합입니다. 이 변경 내용 집합은 동기화 정보를 고려하여 줄일 수 있습니다.

InsertCommand, UpdateCommandDeleteCommand

다른 노드에서 선택한 삽입, 업데이트 및 삭제를 노드에 적용합니다. 이러한 변경 내용은 SelectIncrementalChangesCommand 속성에 지정한 쿼리 또는 프로시저를 사용하여 선택되었습니다.

InsertMetadataCommand, UpdateMetadataCommandDeleteMetadataCommand

노드의 변경 내용 추적 테이블을 업데이트하여 노드에서 선택되고 다른 노드에 적용된 변경 내용을 반영합니다. Sync Framework에서는 이러한 업데이트를 통해 변경이 수행된 위치와 시간을 추적할 수 있습니다.

SelectRowCommand

동기화 중에 충돌한 행의 메타데이터를 선택합니다.

SelectMetadataForCleanupCommand

노드에서 정리할 수 있는 메타데이터를 선택합니다. 정리는 일반적으로 유지 기간을 따르며, 일정 시간 동안 메타데이터가 유지됩니다. 그러나 응용 프로그램에서는 다른 논리를 사용하여 메타데이터를 정리할 시점을 결정할 수 있습니다. 자세한 내용은 방법: 공동 작업 동기화의 메타데이터 정리(SQL Server 이외)를 참조하십시오.

이 섹션의 코드 예제에서는 동기화 중에 DbSyncAdapter 개체에서 실행되는 명령을 만듭니다. 이러한 명령은 방법: 공동 작업 동기화를 위한 서버 데이터베이스 프로비전(SQL Server 이외)에 설명된 저장 프로시저를 호출합니다. 전체 명령 집합은 이 항목의 끝 부분에 나오는 전체 코드 예제에 포함되어 있습니다. 코드에서 "@" + DbSyncSession 형식의 매개 변수는 세션 변수입니다. 예를 들어 "@" + DbSyncSession.SyncMinTimestamp@sync_min_timestamp 값을 확인하는 변수입니다. 자세한 내용은 방법: 공동 작업 동기화에 세션 변수 사용을 참조하십시오.

SelectIncrementalChangesCommand에 대한 응용 프로그램 코드

SqlCommand chgsCustomerCmd = new SqlCommand();
chgsCustomerCmd.CommandType = CommandType.StoredProcedure;
chgsCustomerCmd.CommandText = "Sync.sp_Customer_SelectChanges";
chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMetadataOnly, SqlDbType.Int);
chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncInitialize, SqlDbType.Int);

adapterCustomer.SelectIncrementalChangesCommand = chgsCustomerCmd;
Dim chgsCustomerCmd As New SqlCommand()
With chgsCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_SelectChanges"
    .Parameters.Add("@" & DbSyncSession.SyncMetadataOnly, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncInitialize, SqlDbType.Int)
End With

adapterCustomer.SelectIncrementalChangesCommand = chgsCustomerCmd

UpdateCommand에 대한 응용 프로그램 코드

SqlCommand updCustomerCmd = new SqlCommand();
updCustomerCmd.CommandType = CommandType.StoredProcedure;
updCustomerCmd.CommandText = "Sync.sp_Customer_ApplyUpdate";
updCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
updCustomerCmd.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
updCustomerCmd.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
updCustomerCmd.Parameters.Add("@CustomerType", SqlDbType.NVarChar);
updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;
updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncForceWrite, SqlDbType.Int);

adapterCustomer.UpdateCommand = updCustomerCmd;
Dim updCustomerCmd As New SqlCommand()
With updCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_ApplyUpdate"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
    .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
    .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
    .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
    .Parameters.Add("@" & DbSyncSession.SyncForceWrite, SqlDbType.Int)
End With

adapterCustomer.UpdateCommand = updCustomerCmd

UpdateMetadataCommand에 대한 응용 프로그램 코드

SqlCommand updMetadataCustomerCmd = new SqlCommand();
updMetadataCustomerCmd.CommandType = CommandType.StoredProcedure;
updMetadataCustomerCmd.CommandText = "Sync.sp_Customer_UpdateMetadata";
updMetadataCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerKey, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowIsTombstone, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

adapterCustomer.UpdateMetadataCommand = updMetadataCustomerCmd;
Dim updMetadataCustomerCmd As New SqlCommand()
With updMetadataCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_UpdateMetadata"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncCreatePeerKey, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt)
    .Parameters.Add("@" & DbSyncSession.SyncRowIsTombstone, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
    .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
End With

adapterCustomer.UpdateMetadataCommand = updMetadataCustomerCmd

SelectRowCommand에 대한 응용 프로그램 코드

SqlCommand selRowCustomerCmd = new SqlCommand();
selRowCustomerCmd.CommandType = CommandType.StoredProcedure;
selRowCustomerCmd.CommandText = "Sync.sp_Customer_SelectRow";
selRowCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
selRowCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);

adapterCustomer.SelectRowCommand = selRowCustomerCmd;
Dim selRowCustomerCmd As New SqlCommand()
With selRowCustomerCmd
    .CommandType = CommandType.StoredProcedure
    .CommandText = "Sync.sp_Customer_SelectRow"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
End With

adapterCustomer.SelectRowCommand = selRowCustomerCmd

모든 명령이 정의된 후에는 다음 코드를 사용하여 어댑터가 공급자에 추가됩니다.

sampleProvider.SyncAdapters.Add(adapterCustomer);

서버 동기화 공급자

다음 속성은 서버의 DbSyncProvider 개체에서 설정됩니다.

동기화 공급자 속성 용도

ScopeName

특정 세션에 대해 동기화할 범위의 이름입니다. 각 범위는 서버 데이터베이스의 범위 정보 테이블에 이미 있어야 합니다.

Connection

Sync Framework에서 변경 내용을 선택하고 서버 데이터베이스에 적용하는 데 사용되는 연결입니다.

SelectNewTimestampCommand

변경 내용 집합을 선택하여 각 데이터베이스에 적용하는 데 사용되는 타임스탬프 값을 반환합니다. 현재 동기화 세션 중에 이 명령은 새 타임스탬프 값을 제공합니다. 이전 동기화 세션의 타임스탬프 값 이후와 새 타임스탬프 값 이전에 수행된 변경 내용이 동기화됩니다. 그런 다음 새 값이 저장되어 다음 세션의 시작점으로 사용됩니다.

SelectTableMaxTimestampsCommand

각 기본 테이블 또는 추적 테이블에서 최대 타임스탬프를 선택하여 각 테이블에 대해 원본의 변경 내용이 모두 대상에 이미 있는지 여부를 확인합니다. 대상에 변경 내용이 이미 있으면 Sync Framework에서 종종 열거형 쿼리를 생략할 수 있으므로 성능이 향상될 수 있습니다.

이 속성은 선택 사항입니다. 명령을 지정하지 않으면 모든 테이블에 대해 열거형 쿼리가 실행됩니다.

SelectScopeInfoCommand

범위 정보 테이블에서 정보(예: Sync Framework에서 필요한 동기화 정보 및 정리 정보)를 반환합니다.

UpdateScopeInfoCommand

범위 정보 테이블의 정보를 업데이트합니다.

SelectOverlappingScopesCommand

지정된 범위의 테이블 중 다른 범위에도 포함된 모든 테이블에 대한 범위 이름과 테이블 이름을 반환합니다.

UpdateScopeCleanupTimestampCommand

범위 정보 테이블의 특정 범위에 대한 scope_cleanup_timestamp 열을 업데이트하여 범위에서 정리가 수행된 지점을 표시합니다. 자세한 내용은 방법: 공동 작업 동기화의 메타데이터 정리(SQL Server 이외)를 참조하십시오.

이 섹션의 코드 예제에서는 동기화 중에 DbSyncProvider 개체에서 실행되는 명령을 만듭니다. SelectScopeInfoCommandUpdateScopeInfoCommand 속성에 대한 명령은 이 항목의 끝 부분에 나오는 전체 코드 예제에 포함되어 있습니다.

ScopeName 및 Connection에 대한 응용 프로그램 코드

다음 코드 예제에서는 동기화할 범위의 이름과 동기화하는 데 사용할 연결을 설정합니다. 전체 코드 예제에서는 DbSyncProvider 개체에 Customer 테이블의 DbSyncAdapter 개체가 포함되어 있습니다. 맨 처음 이 테이블이 동기화될 때 Sales 범위의 정의가 설정됩니다. 일단 범위를 동기화한 후에는 범위를 변경하면 안 됩니다. 범위에 있는 테이블을 변경하거나 이러한 테이블의 절을 필터링하면 데이터 불일치가 발생할 수 있습니다.

SqlConnection peerConnection = new SqlConnection(peerConnString);
sampleDbProvider.Connection = peerConnection;
sampleDbProvider.ScopeName = "Sales";
Dim peerConnection As New SqlConnection(peerConnString)
sampleDbProvider.Connection = peerConnection
sampleDbProvider.ScopeName = "Sales"

범위에 대한 자세한 내용은 방법: 공동 작업 동기화를 위한 서버 데이터베이스 프로비전(SQL Server 이외)에서 "동기화할 범위 정의"를 참조하십시오.

SelectNewTimestampCommand에 대한 응용 프로그램 코드

다음 코드 예제에서는 SelectNewTimestampCommand 속성에 대한 명령을 만듭니다.

SqlCommand selectNewTimestampCommand = new SqlCommand();
string newTimestampVariable = "@" + DbSyncSession.SyncNewTimestamp;
selectNewTimestampCommand.CommandText = "SELECT " + newTimestampVariable + " = min_active_rowversion() - 1";
selectNewTimestampCommand.Parameters.Add(newTimestampVariable, SqlDbType.Timestamp);
selectNewTimestampCommand.Parameters[newTimestampVariable].Direction = ParameterDirection.Output;

sampleDbProvider.SelectNewTimestampCommand = selectNewTimestampCommand;
Dim selectNewTimestampCommand As New SqlCommand()
Dim newTimestampVariable As String = "@" & DbSyncSession.SyncNewTimestamp
With selectNewTimestampCommand
    .CommandText = "SELECT " & newTimestampVariable & " = min_active_rowversion() - 1"
    .Parameters.Add(newTimestampVariable, SqlDbType.Timestamp)
    .Parameters(newTimestampVariable).Direction = ParameterDirection.Output
End With

sampleDbProvider.SelectNewTimestampCommand = selectNewTimestampCommand

SelectTableMaxTimestampsCommand에 대한 응용 프로그램 코드

다음 코드 예제에서는 SelectTableMaxTimestampsCommand 속성에 대한 명령을 만듭니다. 전체 코드 예제에서는 노드가 변경되지 않은 동기화 세션이 있습니다. 이러한 세션에서 SelectTableMaxTimestampsCommand 호출을 통해 동기화할 데이터 변경 내용이 없다는 것을 알 수 있으므로 SelectIncrementalChangesCommand가 호출되지 않습니다.

SqlCommand selTableMaxTsCmd = new SqlCommand();
selTableMaxTsCmd.CommandType = CommandType.Text;
selTableMaxTsCmd.CommandText = "SELECT 'Customer' AS table_name, " +
                               "MAX(local_update_peer_timestamp) AS max_timestamp " +
                               "FROM Sync.Customer_Tracking";
sampleDbProvider.SelectTableMaxTimestampsCommand = selTableMaxTsCmd;
Dim selTableMaxTsCmd As New SqlCommand()
selTableMaxTsCmd.CommandType = CommandType.Text
selTableMaxTsCmd.CommandText = "SELECT 'Customer' AS table_name, " _
                             & "MAX(local_update_peer_timestamp) AS max_timestamp " _
                             & "FROM Sync.Customer_Tracking"
sampleDbProvider.SelectTableMaxTimestampsCommand = selTableMaxTsCmd

클라이언트 동기화 공급자

다음 코드 예제에서는 SqlCeSyncProvider 개체를 만들고, 범위 및 연결을 설정하고, 이벤트 처리기를 등록합니다. SqlCeSyncProvider의 경우 어댑터는 전용으로 사용되며 클라이언트 데이터베이스에서 초기화된 스키마에 따라 명령이 자동으로 만들어집니다.

public SqlCeSyncProvider ConfigureCeSyncProvider(string sqlCeConnString)
{
    
    SqlCeSyncProvider sampleCeProvider = new SqlCeSyncProvider();
    
    //Set the scope name
    sampleCeProvider.ScopeName = "Sales";
    
    //Set the connection
    sampleCeProvider.Connection = new SqlCeConnection(sqlCeConnString);

    //Register event handlers

    //Register the BeginSnapshotInitialization event handler. 
    //It is called when snapshot initialization is about to begin
    //for a particular scope in a Compact database.
    sampleCeProvider.BeginSnapshotInitialization += new EventHandler<DbBeginSnapshotInitializationEventArgs>(sampleCeProvider_BeginSnapshotInitialization);

    //Register the EndSnapshotInitialization event handler. 
    //It is called when snapshot initialization has completed
    //for a particular scope in a Compact database.
    sampleCeProvider.EndSnapshotInitialization += new EventHandler<DbEndSnapshotInitializationEventArgs>(sampleCeProvider_EndSnapshotInitialization);

    return sampleCeProvider;
}
Public Function ConfigureCeSyncProvider(ByVal sqlCeConnString As String) As SqlCeSyncProvider

    Dim sampleCeProvider As New SqlCeSyncProvider()

    'Set the scope name 
    sampleCeProvider.ScopeName = "Sales"

    'Set the connection 
    sampleCeProvider.Connection = New SqlCeConnection(sqlCeConnString)

    'Register event handlers 

    'Register the BeginSnapshotInitialization event handler. 
    'It is called when snapshot initialization is about to begin 
    'for a particular scope in a Compact database. 
    AddHandler sampleCeProvider.BeginSnapshotInitialization, AddressOf sampleCeProvider_BeginSnapshotInitialization

    'Register the EndSnapshotInitialization event handler. 
    'It is called when snapshot initialization has completed 
    'for a particular scope in a Compact database. 
    AddHandler sampleCeProvider.EndSnapshotInitialization, AddressOf sampleCeProvider_EndSnapshotInitialization

    Return sampleCeProvider
End Function

클라이언트 데이터베이스 초기화

SQL Server Compact 클라이언트 데이터베이스가 다른 데이터베이스에서 변경 내용을 받을 수 있으려면 우선 클라이언트 데이터베이스에 테이블 스키마와 초기 데이터가 있어야 하고 Sync Framework에 필요한 변경 내용 추적 인프라도 있어야 합니다. Sync Framework에서는 데이터베이스를 초기화하는 다음과 같은 두 가지 방법을 제공합니다.

  • 서버 데이터베이스를 사용한 클라이언트 데이터베이스의 전체 초기화

    이렇게 하면 서버 데이터베이스 인스턴스에 연결되는 DbSyncProvider 개체에 의해 노출되는 테이블, 열 및 형식 설명 테이블을 사용하여 클라이언트 데이터베이스에 테이블을 만들 수 있습니다. 그런 다음 클라이언트 데이터베이스가 동기화할 수 있도록 준비되고 서버 데이터베이스의 모든 행이 증분 삽입으로 다운로드됩니다. SelectIncrementalChangesCommand 속성에 지정하는 쿼리 또는 프로시저를 사용하여 서버에서 삽입이 선택됩니다.

  • 기존 클라이언트 데이터베이스를 사용한 클라이언트 데이터베이스의 스냅숏 초기화

    스냅숏 초기화는 클라이언트 데이터베이스를 초기화하는 데 드는 시간을 줄이기 위해 디자인되었습니다. 전체 초기화를 사용하여 한 클라이언트 데이터베이스를 초기화한 후 이 처음 클라이언트 데이터베이스의 스냅숏을 사용하여 이후 데이터베이스를 초기화할 수 있습니다. 스냅숏은 테이블 스키마, 데이터(선택 사항) 및 변경 내용 추적 인프라를 포함하는 특별히 준비된 SQL Server Compact 데이터베이스입니다. 스냅숏이 필요한 각 클라이언트에 이 스냅숏을 복사합니다. 클라이언트에 대한 첫 번째 동기화 세션 동안 클라이언트 관련 메타데이터가 업데이트되고 스냅숏이 만들어진 이후 발생한 모든 변경 내용이 클라이언트 데이터베이스에 다운로드됩니다.

중요

스냅숏은 SQL Server Compact 데이터베이스에 작업이 없을 때만 생성해야 합니다. 스냅숏 생성 중에는 어떠한 형식의 작업도 동시에 실행할 수 없습니다.

다음 코드 예제에서는 전체 초기화를 수행합니다. 코드에서는 동기화가 발생할 때마다 클라이언트 데이터베이스에 스키마가 필요한지 여부를 확인합니다. 공급자가 이미 초기화된 클라이언트 데이터베이스 또는 스냅숏 데이터베이스인 클라이언트 데이터베이스에 연결되어 있으면 ScopeExists 메서드가 true를 반환합니다. 메서드가 false를 반환하면 GetScopeDescription 메서드를 사용하여 스키마가 검색되고 클라이언트 데이터베이스에 적용됩니다.

private void CheckIfProviderNeedsSchema(SqlCeSyncProvider providerToCheck, DbSyncProvider providerWithSchema)
{

    //If one of the providers is a SqlCeSyncProvider and it needs
    //to be initialized, retrieve the schema from the other provider
    //if that provider is a DbSyncProvider; otherwise configure a
    //DbSyncProvider, connect to the server, and retrieve the schema.
    if (providerToCheck != null)
    {
        SqlCeSyncScopeProvisioning ceConfig = new SqlCeSyncScopeProvisioning();
        SqlCeConnection ceConn = (SqlCeConnection)providerToCheck.Connection;
        string scopeName = providerToCheck.ScopeName;
        if (!ceConfig.ScopeExists(scopeName, ceConn))
        {
            DbSyncScopeDescription scopeDesc = providerWithSchema.GetScopeDescription();
            ceConfig.PopulateFromScopeDescription(scopeDesc);
            ceConfig.Apply(ceConn);
        }
    }

 }
    Private Sub CheckIfProviderNeedsSchema(ByVal providerToCheck As SqlCeSyncProvider, _
                                           ByVal providerWithSchema As DbSyncProvider)

        'If one of the providers is a SqlCeSyncProvider and it needs 
        'to be initialized, retrieve the schema from the other provider 
        'if that provider is a DbSyncProvider; otherwise configure a 
        'DbSyncProvider, connect to the server, and retrieve the schema. 
        If providerToCheck IsNot Nothing Then
            Dim ceConfig As New SqlCeSyncScopeProvisioning()
            Dim ceConn As SqlCeConnection = DirectCast(providerToCheck.Connection, SqlCeConnection)
            Dim scopeName As String = providerToCheck.ScopeName
            If Not ceConfig.ScopeExists(scopeName, ceConn) Then
                Dim scopeDesc As DbSyncScopeDescription = providerWithSchema.GetScopeDescription()
                ceConfig.PopulateFromScopeDescription(scopeDesc)
                ceConfig.Apply(ceConn)
            End If

        End If

    End Sub
End Class

다음 코드 예제에서는 스냅숏을 생성합니다. 코드에서는 샘플의 ConfigureCESyncProvider 메서드를 사용하여 방금 동기화한 SQL Server Compact 데이터베이스에 연결합니다. 연결한 후 GenerateSnapshot 메서드가 호출되어 원래 데이터베이스의 복사본을 만듭니다. 클라이언트 공급자가 이후에 이 새로운 복사본에 연결되고 동기화가 실행되면 데이터베이스가 초기화됩니다.

//Second session: Synchronize two databases by using one instance of 
//DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
//database is initialized, it is copied by using GenerateSnapshot and then
//used for the third session.
sampleSyncAgent = new SampleSyncAgent(
                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1));
syncStatistics = sampleSyncAgent.Synchronize();
sampleStats.DisplayStats(syncStatistics, "initial");

//Copy the Compact database and save it as SyncSampleClient2.sdf.
SqlCeSyncStoreSnapshotInitialization snapshotInit = new SqlCeSyncStoreSnapshotInitialization();
snapshotInit.GenerateSnapshot(new SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf");

//Make a change that is synchronized during the third session.
Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer");

//Third session: Synchronize the new Compact database. The five rows  
//from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
//change is now downloaded to bring SyncSampleClient2.sdf up to date.
//SyncSampleClient2.sdf will get this row during the next round of
//synchronization sessions.
sampleSyncAgent = new SampleSyncAgent(
                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2));
syncStatistics = sampleSyncAgent.Synchronize();
sampleStats.DisplayStats(syncStatistics, "initial");
'Second session: Synchronize two databases by using one instance of 
'DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
'database is initialized, it is copied by using GenerateSnapshot and then 
'used for the third session. 
sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                      sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1))
syncStatistics = sampleSyncAgent.Synchronize()
sampleStats.DisplayStats(syncStatistics, "initial")

'Copy the Compact database and save it as SyncSampleClient2.sdf. 
Dim snapshotInit As New SqlCeSyncStoreSnapshotInitialization()
snapshotInit.GenerateSnapshot(New SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf")

'Make a change that is synchronized during the third session. 
Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer")

'Third session: Synchronize the new Compact database. The five rows 
'from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
'change is now downloaded to bring SyncSampleClient2.sdf up to date. 
'SyncSampleClient2.sdf will get this row during the next round of 
'synchronization sessions. 
sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                      sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2))
syncStatistics = sampleSyncAgent.Synchronize()
sampleStats.DisplayStats(syncStatistics, "initial")

참고

이 예제에서는 기본 응용 프로그램이 실행되는 동안 스냅숏 데이터베이스가 생성됩니다. 대부분의 경우 스냅숏은 다른 동기화 작업과 별개로 사용량이 많지 않은 시간대에 생성됩니다.

동기화 조정자 만들기 및 데이터베이스 동기화

이 항목의 이전 섹션에서 설명한 코드에서는 동기화에 필요한 속성을 설정하는 방법을 보여 줍니다. 이제 노드를 실제로 동기화할 차례입니다. 노드는 항상 SyncSamplesDb_Peer1SyncSampleCe2처럼 쌍으로 동기화됩니다. 프로덕션 응용 프로그램의 경우에는 일반적으로 어떠한 노드에서도 동기화를 시작할 수 있도록 각 노드에 응용 프로그램의 복사본을 배포합니다.

다음 코드 예제에서는 SyncOrchestrator에서 파생되는 SampleSyncAgent 클래스를 만듭니다. SampleSyncAgent 생성자에는 두 RelationalSyncProvider 개체가 있습니다. DbSyncProviderSqlCeSyncProvider 모두 RelationalSyncProvider에서 파생되므로 SampleSyncAgent 개체는 두 공급자 형식의 모든 조합을 동기화할 수 있습니다. 이 코드에서는 로컬 공급자와 원격 공급자를 지정하고, 변경 내용을 우선 원격 데이터베이스에서 로컬 데이터베이스로 업로드한 다음 반대 방향으로 다운로드하도록 지정합니다. 코드에서는 공급자 중 하나가 클라이언트 데이터베이스의 스키마 초기화가 필요한 SqlCeSyncProvider 개체인지 여부를 확인합니다.

public class SampleSyncAgent : SyncOrchestrator
{
    public SampleSyncAgent(RelationalSyncProvider localProvider, RelationalSyncProvider remoteProvider)
    {

        this.LocalProvider = localProvider;
        this.RemoteProvider = remoteProvider;
        this.Direction = SyncDirectionOrder.UploadAndDownload;

        //Check to see if any provider is a SqlCe provider and if it needs to
        //be initialized.
        CheckIfProviderNeedsSchema(localProvider as SqlCeSyncProvider, remoteProvider as DbSyncProvider);
        CheckIfProviderNeedsSchema(remoteProvider as SqlCeSyncProvider, localProvider as DbSyncProvider);
 
    }

    //For Compact databases that are not initialized with a snapshot,
    //get the schema and initial data from a server database.
    private void CheckIfProviderNeedsSchema(SqlCeSyncProvider providerToCheck, DbSyncProvider providerWithSchema)
    {

        //If one of the providers is a SqlCeSyncProvider and it needs
        //to be initialized, retrieve the schema from the other provider
        //if that provider is a DbSyncProvider; otherwise configure a
        //DbSyncProvider, connect to the server, and retrieve the schema.
        if (providerToCheck != null)
        {
            SqlCeSyncScopeProvisioning ceConfig = new SqlCeSyncScopeProvisioning();
            SqlCeConnection ceConn = (SqlCeConnection)providerToCheck.Connection;
            string scopeName = providerToCheck.ScopeName;
            if (!ceConfig.ScopeExists(scopeName, ceConn))
            {
                DbSyncScopeDescription scopeDesc = providerWithSchema.GetScopeDescription();
                ceConfig.PopulateFromScopeDescription(scopeDesc);
                ceConfig.Apply(ceConn);
            }
        }

     }
}
Public Class SampleSyncAgent
    Inherits SyncOrchestrator
    Public Sub New(ByVal localProvider As RelationalSyncProvider, ByVal remoteProvider As RelationalSyncProvider)

        Me.LocalProvider = localProvider
        Me.RemoteProvider = remoteProvider
        Me.Direction = SyncDirectionOrder.UploadAndDownload

        'Check to see if any provider is a SqlCe provider and if it needs to 
        'be initialized. 
        CheckIfProviderNeedsSchema(TryCast(localProvider, SqlCeSyncProvider), _
                                   TryCast(remoteProvider, DbSyncProvider))
        CheckIfProviderNeedsSchema(TryCast(remoteProvider, SqlCeSyncProvider), _
                                   TryCast(localProvider, DbSyncProvider))

    End Sub

    'For Compact databases that are not initialized with a snapshot, 
    'get the schema and initial data from a server database. 
    Private Sub CheckIfProviderNeedsSchema(ByVal providerToCheck As SqlCeSyncProvider, _
                                           ByVal providerWithSchema As DbSyncProvider)

        'If one of the providers is a SqlCeSyncProvider and it needs 
        'to be initialized, retrieve the schema from the other provider 
        'if that provider is a DbSyncProvider; otherwise configure a 
        'DbSyncProvider, connect to the server, and retrieve the schema. 
        If providerToCheck IsNot Nothing Then
            Dim ceConfig As New SqlCeSyncScopeProvisioning()
            Dim ceConn As SqlCeConnection = DirectCast(providerToCheck.Connection, SqlCeConnection)
            Dim scopeName As String = providerToCheck.ScopeName
            If Not ceConfig.ScopeExists(scopeName, ceConn) Then
                Dim scopeDesc As DbSyncScopeDescription = providerWithSchema.GetScopeDescription()
                ceConfig.PopulateFromScopeDescription(scopeDesc)
                ceConfig.Apply(ceConn)
            End If

        End If

    End Sub
End Class

다음 코드에서는 예제 응용 프로그램에서 각 세션에 적절한 공급자 속성을 설정하는 데 사용하는 ConfigureDbSyncProvider 또는 ConfigureCeSyncProvider 메서드를 호출하여 동기화 세션을 설정합니다. 그런 다음 코드에서 SampleSyncAgentSynchronize 메서드를 호출하여 각 데이터베이스 쌍을 동기화합니다.

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

try
{
    //Initial synchronization. Instantiate the SyncOrchestrator
    //and call Synchronize.
    SampleSyncProvider sampleSyncProvider = new SampleSyncProvider();
    SyncOrchestrator sampleSyncAgent;
    SyncOperationStatistics syncStatistics;

    //First session: Synchronize two databases by using two instances 
    //of DbSyncProvider.
    sampleSyncAgent = new SampleSyncAgent(
                            sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                            sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2));
    syncStatistics = sampleSyncAgent.Synchronize();
    sampleStats.DisplayStats(syncStatistics, "initial");

    //Second session: Synchronize two databases by using one instance of 
    //DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
    //database is initialized, it is copied by using GenerateSnapshot and then
    //used for the third session.
    sampleSyncAgent = new SampleSyncAgent(
                            sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                            sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1));
    syncStatistics = sampleSyncAgent.Synchronize();
    sampleStats.DisplayStats(syncStatistics, "initial");

    //Copy the Compact database and save it as SyncSampleClient2.sdf.
    SqlCeSyncStoreSnapshotInitialization snapshotInit = new SqlCeSyncStoreSnapshotInitialization();
    snapshotInit.GenerateSnapshot(new SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf");

    //Make a change that is synchronized during the third session.
    Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer");

    //Third session: Synchronize the new Compact database. The five rows  
    //from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
    //change is now downloaded to bring SyncSampleClient2.sdf up to date.
    //SyncSampleClient2.sdf will get this row during the next round of
    //synchronization sessions.
    sampleSyncAgent = new SampleSyncAgent(
                            sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                            sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2));
    syncStatistics = sampleSyncAgent.Synchronize();
    sampleStats.DisplayStats(syncStatistics, "initial");
}


catch (DbOutdatedSyncException ex)
{
    Console.WriteLine("Outdated Knowledge: " + ex.OutdatedPeerSyncKnowledge.ToString() +
                      " Clean up knowledge: " + ex.MissingCleanupKnowledge.ToString());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}
'The SampleStats class handles information from the SyncStatistics 
'object that the Synchronize method returns. 
Dim sampleStats As New SampleStats()

Try
    'Initial synchronization. Instantiate the SyncOrchestrator 
    'and call Synchronize. 
    Dim sampleSyncProvider As New SampleSyncProvider()
    Dim sampleSyncAgent As SyncOrchestrator
    Dim syncStatistics As SyncOperationStatistics

    'First session: Synchronize two databases by using two instances 
    'of DbSyncProvider. 
    sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                          sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2))
    syncStatistics = sampleSyncAgent.Synchronize()
    sampleStats.DisplayStats(syncStatistics, "initial")

    'Second session: Synchronize two databases by using one instance of 
    'DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
    'database is initialized, it is copied by using GenerateSnapshot and then 
    'used for the third session. 
    sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                          sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1))
    syncStatistics = sampleSyncAgent.Synchronize()
    sampleStats.DisplayStats(syncStatistics, "initial")

    'Copy the Compact database and save it as SyncSampleClient2.sdf. 
    Dim snapshotInit As New SqlCeSyncStoreSnapshotInitialization()
    snapshotInit.GenerateSnapshot(New SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf")

    'Make a change that is synchronized during the third session. 
    Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer")

    'Third session: Synchronize the new Compact database. The five rows 
    'from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
    'change is now downloaded to bring SyncSampleClient2.sdf up to date. 
    'SyncSampleClient2.sdf will get this row during the next round of 
    'synchronization sessions. 
    sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                          sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2))
    syncStatistics = sampleSyncAgent.Synchronize()
    sampleStats.DisplayStats(syncStatistics, "initial")
Catch ex As DbOutdatedSyncException


    Console.WriteLine(("Outdated Knowledge: " & ex.OutdatedPeerSyncKnowledge.ToString() & " Clean up knowledge: ") _
                      & ex.MissingCleanupKnowledge.ToString())
Catch ex As Exception
    Console.WriteLine(ex.Message)
End Try

전체 코드 예제

다음의 전체 코드 예제에는 앞에서 설명한 코드 예제와 동기화 통계 및 이벤트 정보를 표시하는 추가 코드가 포함되어 있습니다. 이 예제를 사용하려면 데이터베이스 공급자용 유틸리티 클래스 방법 항목에 나와 있는 Utility 클래스가 필요합니다.

using System;
using System.Collections.Generic;
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.SqlServerCe;

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

            //The Utility class handles all functionality that is not
            //directly related to synchronization, such as holding peerConnection 
            //string information and making changes to the server database.
            Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync1, true);
            Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync2, false);

            //The SampleStats class handles information from the SyncStatistics
            //object that the Synchronize method returns.
            SampleStats sampleStats = new SampleStats();
            
            try
            {
                //Initial synchronization. Instantiate the SyncOrchestrator
                //and call Synchronize.
                SampleSyncProvider sampleSyncProvider = new SampleSyncProvider();
                SyncOrchestrator sampleSyncAgent;
                SyncOperationStatistics syncStatistics;

                //First session: Synchronize two databases by using two instances 
                //of DbSyncProvider.
                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "initial");

                //Second session: Synchronize two databases by using one instance of 
                //DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
                //database is initialized, it is copied by using GenerateSnapshot and then
                //used for the third session.
                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "initial");

                //Copy the Compact database and save it as SyncSampleClient2.sdf.
                SqlCeSyncStoreSnapshotInitialization snapshotInit = new SqlCeSyncStoreSnapshotInitialization();
                snapshotInit.GenerateSnapshot(new SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf");

                //Make a change that is synchronized during the third session.
                Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer");

                //Third session: Synchronize the new Compact database. The five rows  
                //from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
                //change is now downloaded to bring SyncSampleClient2.sdf up to date.
                //SyncSampleClient2.sdf will get this row during the next round of
                //synchronization sessions.
                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "initial");
            }


            catch (DbOutdatedSyncException ex)
            {
                Console.WriteLine("Outdated Knowledge: " + ex.OutdatedPeerSyncKnowledge.ToString() +
                                  " Clean up knowledge: " + ex.MissingCleanupKnowledge.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            //Make a change in one of the databases.
            Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync2, "Customer");

            try
            {
                //Subsequent synchronization. Changes are now synchronized between all
                //nodes.
                SampleSyncProvider sampleSyncProvider = new SampleSyncProvider();
                SyncOrchestrator sampleSyncAgent;
                SyncOperationStatistics syncStatistics;

                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "subsequent");

                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "subsequent");

                sampleSyncAgent = new SampleSyncAgent(
                                        sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1),
                                        sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2));
                syncStatistics = sampleSyncAgent.Synchronize();
                sampleStats.DisplayStats(syncStatistics, "subsequent");

            }


            catch (DbOutdatedSyncException ex)
            {
                Console.WriteLine("Outdated Knowledge: " + ex.OutdatedPeerSyncKnowledge.ToString() +
                                  " Clean up knowledge: " + ex.MissingCleanupKnowledge.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            //Return data back to its original state.
            Utility.CleanUpNode(Utility.ConnStr_DbSync1);
            Utility.CleanUpNode(Utility.ConnStr_DbSync2);

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

        //Create a class that is derived from 
        //Microsoft.Synchronization.SyncOrchestrator.
        public class SampleSyncAgent : SyncOrchestrator
        {
            public SampleSyncAgent(RelationalSyncProvider localProvider, RelationalSyncProvider remoteProvider)
            {

                this.LocalProvider = localProvider;
                this.RemoteProvider = remoteProvider;
                this.Direction = SyncDirectionOrder.UploadAndDownload;

                //Check to see if any provider is a SqlCe provider and if it needs to
                //be initialized.
                CheckIfProviderNeedsSchema(localProvider as SqlCeSyncProvider, remoteProvider as DbSyncProvider);
                CheckIfProviderNeedsSchema(remoteProvider as SqlCeSyncProvider, localProvider as DbSyncProvider);
         
            }

            //For Compact databases that are not initialized with a snapshot,
            //get the schema and initial data from a server database.
            private void CheckIfProviderNeedsSchema(SqlCeSyncProvider providerToCheck, DbSyncProvider providerWithSchema)
            {

                //If one of the providers is a SqlCeSyncProvider and it needs
                //to be initialized, retrieve the schema from the other provider
                //if that provider is a DbSyncProvider; otherwise configure a
                //DbSyncProvider, connect to the server, and retrieve the schema.
                if (providerToCheck != null)
                {
                    SqlCeSyncScopeProvisioning ceConfig = new SqlCeSyncScopeProvisioning();
                    SqlCeConnection ceConn = (SqlCeConnection)providerToCheck.Connection;
                    string scopeName = providerToCheck.ScopeName;
                    if (!ceConfig.ScopeExists(scopeName, ceConn))
                    {
                        DbSyncScopeDescription scopeDesc = providerWithSchema.GetScopeDescription();
                        ceConfig.PopulateFromScopeDescription(scopeDesc);
                        ceConfig.Apply(ceConn);
                    }
                }

             }
        }


        public class SampleSyncProvider
        {

            public SqlCeSyncProvider ConfigureCeSyncProvider(string sqlCeConnString)
            {
                
                SqlCeSyncProvider sampleCeProvider = new SqlCeSyncProvider();
                
                //Set the scope name
                sampleCeProvider.ScopeName = "Sales";
                
                //Set the connection
                sampleCeProvider.Connection = new SqlCeConnection(sqlCeConnString);

                //Register event handlers

                //Register the BeginSnapshotInitialization event handler. 
                //It is called when snapshot initialization is about to begin
                //for a particular scope in a Compact database.
                sampleCeProvider.BeginSnapshotInitialization += new EventHandler<DbBeginSnapshotInitializationEventArgs>(sampleCeProvider_BeginSnapshotInitialization);

                //Register the EndSnapshotInitialization event handler. 
                //It is called when snapshot initialization has completed
                //for a particular scope in a Compact database.
                sampleCeProvider.EndSnapshotInitialization += new EventHandler<DbEndSnapshotInitializationEventArgs>(sampleCeProvider_EndSnapshotInitialization);

                return sampleCeProvider;
            }

            public void sampleCeProvider_CreatingSchema(object sender, CreatingSchemaEventArgs e)
            {
                Console.WriteLine("Full Initialization Process Started.....");
                Console.WriteLine(
                    string.Format("CreatingSchame Event fired for Database {0}", e.Connection.Database)
                    );
            }

            public void sampleCeProvider_BeginSnapshotInitialization(object sender, DbBeginSnapshotInitializationEventArgs e)
            {
                Console.WriteLine("");
                Console.WriteLine("Snapshot initialization process started");
                Console.WriteLine(
                    string.Format("BeginSnapshotInitialization event fired for scope {0}", e.ScopeName)
                    );
            }

            public void sampleCeProvider_EndSnapshotInitialization(object sender, DbEndSnapshotInitializationEventArgs e)
            {
                Console.WriteLine("EndSnapshotInitialization event fired");
                
                Dictionary<string, DbSnapshotInitializationTableStatistics> tableStats = 
                    e.TableInitializationStatistics;

                foreach (string tableName in tableStats.Keys)
                {
                    
                    DbSnapshotInitializationTableStatistics ts = tableStats[tableName];
                    
                    Console.WriteLine("\tTable Name: " + tableName);

                    Console.WriteLine("\tTotal Rows: " + ts.TotalRows);

                    Console.WriteLine("\tRows Intialized: " + ts.RowsInitialized);

                    Console.WriteLine("\tStart Time: " + ts.StartTime);

                    Console.WriteLine("\tEnd Time: " + ts.EndTime);

                }
                
                Console.WriteLine("Snapshot initialization process completed");
                Console.WriteLine("");
            }

            public DbSyncProvider ConfigureDbSyncProvider(string peerConnString)
            {

                DbSyncProvider sampleDbProvider = new DbSyncProvider();

                SqlConnection peerConnection = new SqlConnection(peerConnString);
                sampleDbProvider.Connection = peerConnection;
                sampleDbProvider.ScopeName = "Sales";

                //Create a DbSyncAdapter object for the Customer table and associate it 
                //with the DbSyncProvider. Following the DataAdapter style in ADO.NET, 
                //DbSyncAdapter is the equivalent for synchronization. The commands that 
                //are specified for the DbSyncAdapter object call stored procedures
                //that are created in each peer database.
                DbSyncAdapter adapterCustomer = new DbSyncAdapter("Customer");


                //Specify the primary key, which Sync Framework uses
                //to identify each row during synchronization.
                adapterCustomer.RowIdColumns.Add("CustomerId");


                //Specify the command to select incremental changes.
                //In this command and other commands, session variables are
                //used to pass information at runtime. DbSyncSession.SyncMetadataOnly 
                //and SyncMinTimestamp are two of the string constants that
                //the DbSyncSession class exposes. You could also include 
                //@sync_metadata_only and @sync_min_timestamp directly in your 
                //queries:
                //*  sync_metadata_only is used by Sync Framework as an optimization
                //   in some queries.
                //* The value of the sync_min_timestamp session variable is compared to
                //   values in the sync_row_timestamp column in the tracking table to 
                //   determine which rows to select.
                SqlCommand chgsCustomerCmd = new SqlCommand();
                chgsCustomerCmd.CommandType = CommandType.StoredProcedure;
                chgsCustomerCmd.CommandText = "Sync.sp_Customer_SelectChanges";
                chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMetadataOnly, SqlDbType.Int);
                chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
                chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
                chgsCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncInitialize, SqlDbType.Int);

                adapterCustomer.SelectIncrementalChangesCommand = chgsCustomerCmd;

                //Specify the command to insert rows.
                //The sync_row_count session variable is used in this command 
                //and other commands to return a count of the rows affected by an operation. 
                //A count of 0 indicates that an operation failed.
                SqlCommand insCustomerCmd = new SqlCommand();
                insCustomerCmd.CommandType = CommandType.StoredProcedure;
                insCustomerCmd.CommandText = "Sync.sp_Customer_ApplyInsert";
                insCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                insCustomerCmd.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
                insCustomerCmd.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
                insCustomerCmd.Parameters.Add("@CustomerType", SqlDbType.NVarChar);
                insCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

                adapterCustomer.InsertCommand = insCustomerCmd;


                //Specify the command to update rows.
                //The value of the sync_min_timestamp session variable is compared to
                //values in the sync_row_timestamp column in the tracking table to 
                //determine which rows to update.
                SqlCommand updCustomerCmd = new SqlCommand();
                updCustomerCmd.CommandType = CommandType.StoredProcedure;
                updCustomerCmd.CommandText = "Sync.sp_Customer_ApplyUpdate";
                updCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                updCustomerCmd.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
                updCustomerCmd.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
                updCustomerCmd.Parameters.Add("@CustomerType", SqlDbType.NVarChar);
                updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
                updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;
                updCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncForceWrite, SqlDbType.Int);
                
                adapterCustomer.UpdateCommand = updCustomerCmd;


                //Specify the command to delete rows.
                //The value of the sync_min_timestamp session variable is compared to
                //values in the sync_row_timestamp column in the tracking table to 
                //determine which rows to delete.
                SqlCommand delCustomerCmd = new SqlCommand();
                delCustomerCmd.CommandType = CommandType.StoredProcedure;
                delCustomerCmd.CommandText = "Sync.sp_Customer_ApplyDelete";
                delCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                delCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt);
                delCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;
                delCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncForceWrite, SqlDbType.Int);
                
                adapterCustomer.DeleteCommand = delCustomerCmd;

                //Specify the command to select any conflicting rows.
                SqlCommand selRowCustomerCmd = new SqlCommand();
                selRowCustomerCmd.CommandType = CommandType.StoredProcedure;
                selRowCustomerCmd.CommandText = "Sync.sp_Customer_SelectRow";
                selRowCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                selRowCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);

                adapterCustomer.SelectRowCommand = selRowCustomerCmd;


                //Specify the command to insert metadata rows.
                //The session variables in this command relate to columns in
                //the tracking table.
                SqlCommand insMetadataCustomerCmd = new SqlCommand();
                insMetadataCustomerCmd.CommandType = CommandType.StoredProcedure;
                insMetadataCustomerCmd.CommandText = "Sync.sp_Customer_InsertMetadata";
                insMetadataCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);          
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerKey, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowIsTombstone, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
                insMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

                adapterCustomer.InsertMetadataCommand = insMetadataCustomerCmd;


                //Specify the command to update metadata rows.
                SqlCommand updMetadataCustomerCmd = new SqlCommand();
                updMetadataCustomerCmd.CommandType = CommandType.StoredProcedure;
                updMetadataCustomerCmd.CommandText = "Sync.sp_Customer_UpdateMetadata";
                updMetadataCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncScopeLocalId, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerKey, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowIsTombstone, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
                updMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

                adapterCustomer.UpdateMetadataCommand = updMetadataCustomerCmd;

                //Specify the command to delete metadata rows.
                SqlCommand delMetadataCustomerCmd = new SqlCommand();
                delMetadataCustomerCmd.CommandType = CommandType.StoredProcedure;
                delMetadataCustomerCmd.CommandText = "Sync.sp_Customer_DeleteMetadata";
                delMetadataCustomerCmd.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
                delMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
                delMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt);
                delMetadataCustomerCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;

                adapterCustomer.DeleteMetadataCommand = delMetadataCustomerCmd;


                //Add the adapter to the provider.
                sampleDbProvider.SyncAdapters.Add(adapterCustomer);


                // Configure commands that relate to the provider itself rather 
                // than the DbSyncAdapter object for each table:
                // * SelectNewTimestampCommand: Returns the new high watermark for 
                //   the current synchronization session.
                // * SelectScopeInfoCommand: Returns sync knowledge, cleanup knowledge, 
                //   and a scope version (timestamp).
                // * UpdateScopeInfoCommand: Sets new values for sync knowledge and cleanup knowledge.            
                // * SelectTableMaxTimestampsCommand (optional): Returns the maximum timestamp from each base table 
                //   or tracking table, to determine whether for each table the destination already 
                //   has all of the changes from the source. If a destination table has all the changes,
                //   SelectIncrementalChangesCommand is not called for that table.
                // There are additional commands related to metadata cleanup that are not 
                // included in this application.


                //Select a new timestamp.
                //During each synchronization, the new value and
                //the last value from the previous synchronization
                //are used: the set of changes between these upper and
                //lower bounds is synchronized.
                SqlCommand selectNewTimestampCommand = new SqlCommand();
                string newTimestampVariable = "@" + DbSyncSession.SyncNewTimestamp;
                selectNewTimestampCommand.CommandText = "SELECT " + newTimestampVariable + " = min_active_rowversion() - 1";
                selectNewTimestampCommand.Parameters.Add(newTimestampVariable, SqlDbType.Timestamp);
                selectNewTimestampCommand.Parameters[newTimestampVariable].Direction = ParameterDirection.Output;
                
                sampleDbProvider.SelectNewTimestampCommand = selectNewTimestampCommand;

                //Specify the command to select local replica metadata.
                SqlCommand selReplicaInfoCmd = new SqlCommand();
                selReplicaInfoCmd.CommandType = CommandType.Text;
                selReplicaInfoCmd.CommandText = "SELECT " +
                                                "scope_id, " +
                                                "scope_local_id, " +
                                                "scope_sync_knowledge, " +
                                                "scope_tombstone_cleanup_knowledge, " +
                                                "scope_timestamp " +
                                                "FROM Sync.ScopeInfo " +
                                                "WHERE scope_name = @" + DbSyncSession.SyncScopeName;
                selReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeName, SqlDbType.NVarChar, 100);
                
                sampleDbProvider.SelectScopeInfoCommand = selReplicaInfoCmd;


                //Specify the command to update local replica metadata. 
                SqlCommand updReplicaInfoCmd = new SqlCommand();
                updReplicaInfoCmd.CommandType = CommandType.Text;
                updReplicaInfoCmd.CommandText = "UPDATE  Sync.ScopeInfo SET " +
                                                "scope_sync_knowledge = @" + DbSyncSession.SyncScopeKnowledge + ", " +
                                                "scope_id = @" + DbSyncSession.SyncScopeId + ", " +
                                                "scope_tombstone_cleanup_knowledge = @" + DbSyncSession.SyncScopeCleanupKnowledge + " " +
                                                "WHERE scope_name = @" + DbSyncSession.SyncScopeName + " AND " +
                                                " ( @" + DbSyncSession.SyncCheckConcurrency + " = 0 OR scope_timestamp = @" + DbSyncSession.SyncScopeTimestamp + "); " +
                                                "SET @" + DbSyncSession.SyncRowCount + " = @@rowcount";
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeKnowledge, SqlDbType.VarBinary, 10000);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeCleanupKnowledge, SqlDbType.VarBinary, 10000);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeName, SqlDbType.NVarChar, 100);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncCheckConcurrency, SqlDbType.Int);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeId, SqlDbType.UniqueIdentifier);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncScopeTimestamp, SqlDbType.BigInt);
                updReplicaInfoCmd.Parameters.Add("@" + DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output;
                
                sampleDbProvider.UpdateScopeInfoCommand = updReplicaInfoCmd;


                //Return the maximum timestamp from the Customer_Tracking table.
                //If more tables are synchronized, the query should UNION
                //all of the results. The table name is not schema-qualified
                //in this case because the name was not schema qualified in the
                //DbSyncAdapter constructor.
                SqlCommand selTableMaxTsCmd = new SqlCommand();
                selTableMaxTsCmd.CommandType = CommandType.Text;
                selTableMaxTsCmd.CommandText = "SELECT 'Customer' AS table_name, " +
                                               "MAX(local_update_peer_timestamp) AS max_timestamp " +
                                               "FROM Sync.Customer_Tracking";
                sampleDbProvider.SelectTableMaxTimestampsCommand = selTableMaxTsCmd;

                return sampleDbProvider;
            }
        }

        //Handle the statistics that are returned by the SyncAgent.
        public class SampleStats
        {
            public void DisplayStats(SyncOperationStatistics syncStatistics, string syncType)
            {
                Console.WriteLine(String.Empty);
                if (syncType == "initial")
                {
                    Console.WriteLine("****** Initial Synchronization ******");
                }
                else if (syncType == "subsequent")
                {
                    Console.WriteLine("***** Subsequent Synchronization ****");
                }

                Console.WriteLine("Start Time: " + syncStatistics.SyncStartTime);
                Console.WriteLine("Total Changes Uploaded: " + syncStatistics.UploadChangesTotal);
                Console.WriteLine("Total Changes Downloaded: " + syncStatistics.DownloadChangesTotal);
                Console.WriteLine("Complete Time: " + syncStatistics.SyncEndTime);
                Console.WriteLine(String.Empty);
            }
        }
    }
}
Imports System
Imports System.Collections.Generic
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.SqlServerCe

Class Program

    Shared Sub Main(ByVal args As String())

        'The Utility class handles all functionality that is not 
        'directly related to synchronization, such as holding connection 
        'string information and making changes to the server database.
        Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync1, True)
        Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync2, False)

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

        Try
            'Initial synchronization. Instantiate the SyncOrchestrator 
            'and call Synchronize. 
            Dim sampleSyncProvider As New SampleSyncProvider()
            Dim sampleSyncAgent As SyncOrchestrator
            Dim syncStatistics As SyncOperationStatistics

            'First session: Synchronize two databases by using two instances 
            'of DbSyncProvider. 
            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "initial")

            'Second session: Synchronize two databases by using one instance of 
            'DbSyncProvider and one instance of SqlCeSyncProvider. After the Compact 
            'database is initialized, it is copied by using GenerateSnapshot and then 
            'used for the third session. 
            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "initial")

            'Copy the Compact database and save it as SyncSampleClient2.sdf. 
            Dim snapshotInit As New SqlCeSyncStoreSnapshotInitialization()
            snapshotInit.GenerateSnapshot(New SqlCeConnection(Utility.ConnStr_SqlCeSync1), "SyncSampleClient2.sdf")

            'Make a change that is synchronized during the third session. 
            Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync1, "Customer")

            'Third session: Synchronize the new Compact database. The five rows 
            'from SyncSampleClient1.sdf are already in SyncSampleClient2.sdf. The new 
            'change is now downloaded to bring SyncSampleClient2.sdf up to date. 
            'SyncSampleClient2.sdf will get this row during the next round of 
            'synchronization sessions. 
            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "initial")
        Catch ex As DbOutdatedSyncException


            Console.WriteLine(("Outdated Knowledge: " & ex.OutdatedPeerSyncKnowledge.ToString() & " Clean up knowledge: ") _
                              & ex.MissingCleanupKnowledge.ToString())
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try

        'Make a change in one of the databases. 
        Utility.MakeDataChangesOnNode(Utility.ConnStr_DbSync2, "Customer")

        Try
            'Subsequent synchronization. Changes are now synchronized between all 
            'nodes. 
            Dim sampleSyncProvider As New SampleSyncProvider()
            Dim sampleSyncAgent As SyncOrchestrator
            Dim syncStatistics As SyncOperationStatistics

            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync2))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "subsequent")

            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync1))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "subsequent")

            sampleSyncAgent = New SampleSyncAgent(sampleSyncProvider.ConfigureDbSyncProvider(Utility.ConnStr_DbSync1), _
                                                  sampleSyncProvider.ConfigureCeSyncProvider(Utility.ConnStr_SqlCeSync2))
            syncStatistics = sampleSyncAgent.Synchronize()
            sampleStats.DisplayStats(syncStatistics, "subsequent")

        Catch ex As DbOutdatedSyncException


            Console.WriteLine(("Outdated Knowledge: " & ex.OutdatedPeerSyncKnowledge.ToString() & " Clean up knowledge: ") _
                              & ex.MissingCleanupKnowledge.ToString())
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try

        'Return data back to its original state. 
        Utility.CleanUpNode(Utility.ConnStr_DbSync1)
        Utility.CleanUpNode(Utility.ConnStr_DbSync2)

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

    'Create a class that is derived from 
    'Microsoft.Synchronization.SyncOrchestrator. 
    Public Class SampleSyncAgent
        Inherits SyncOrchestrator
        Public Sub New(ByVal localProvider As RelationalSyncProvider, ByVal remoteProvider As RelationalSyncProvider)

            Me.LocalProvider = localProvider
            Me.RemoteProvider = remoteProvider
            Me.Direction = SyncDirectionOrder.UploadAndDownload

            'Check to see if any provider is a SqlCe provider and if it needs to 
            'be initialized. 
            CheckIfProviderNeedsSchema(TryCast(localProvider, SqlCeSyncProvider), _
                                       TryCast(remoteProvider, DbSyncProvider))
            CheckIfProviderNeedsSchema(TryCast(remoteProvider, SqlCeSyncProvider), _
                                       TryCast(localProvider, DbSyncProvider))

        End Sub

        'For Compact databases that are not initialized with a snapshot, 
        'get the schema and initial data from a server database. 
        Private Sub CheckIfProviderNeedsSchema(ByVal providerToCheck As SqlCeSyncProvider, _
                                               ByVal providerWithSchema As DbSyncProvider)

            'If one of the providers is a SqlCeSyncProvider and it needs 
            'to be initialized, retrieve the schema from the other provider 
            'if that provider is a DbSyncProvider; otherwise configure a 
            'DbSyncProvider, connect to the server, and retrieve the schema. 
            If providerToCheck IsNot Nothing Then
                Dim ceConfig As New SqlCeSyncScopeProvisioning()
                Dim ceConn As SqlCeConnection = DirectCast(providerToCheck.Connection, SqlCeConnection)
                Dim scopeName As String = providerToCheck.ScopeName
                If Not ceConfig.ScopeExists(scopeName, ceConn) Then
                    Dim scopeDesc As DbSyncScopeDescription = providerWithSchema.GetScopeDescription()
                    ceConfig.PopulateFromScopeDescription(scopeDesc)
                    ceConfig.Apply(ceConn)
                End If

            End If

        End Sub
    End Class


    Public Class SampleSyncProvider

        Public Function ConfigureCeSyncProvider(ByVal sqlCeConnString As String) As SqlCeSyncProvider

            Dim sampleCeProvider As New SqlCeSyncProvider()

            'Set the scope name 
            sampleCeProvider.ScopeName = "Sales"

            'Set the connection 
            sampleCeProvider.Connection = New SqlCeConnection(sqlCeConnString)

            'Register event handlers 

            'Register the BeginSnapshotInitialization event handler. 
            'It is called when snapshot initialization is about to begin 
            'for a particular scope in a Compact database. 
            AddHandler sampleCeProvider.BeginSnapshotInitialization, AddressOf sampleCeProvider_BeginSnapshotInitialization

            'Register the EndSnapshotInitialization event handler. 
            'It is called when snapshot initialization has completed 
            'for a particular scope in a Compact database. 
            AddHandler sampleCeProvider.EndSnapshotInitialization, AddressOf sampleCeProvider_EndSnapshotInitialization

            Return sampleCeProvider
        End Function

        Public Sub sampleCeProvider_CreatingSchema(ByVal sender As Object, ByVal e As CreatingSchemaEventArgs)
            Console.WriteLine("Full initialization process started...")
            Console.WriteLine(String.Format("CreatingSchema event fired for database {0}", e.Connection.Database))
        End Sub

        Public Sub sampleCeProvider_BeginSnapshotInitialization(ByVal sender As Object, ByVal e As DbBeginSnapshotInitializationEventArgs)
            Console.WriteLine("")
            Console.WriteLine("Snapshot initialization process started...")
            Console.WriteLine(String.Format("BeginSnapshotInitialization event fired for scope {0}", e.ScopeName))
        End Sub

        Public Sub sampleCeProvider_EndSnapshotInitialization(ByVal sender As Object, ByVal e As DbEndSnapshotInitializationEventArgs)
            Console.WriteLine("EndSnapshotInitialization event fired")

            Dim tableStats As Dictionary(Of String, DbSnapshotInitializationTableStatistics) = e.TableInitializationStatistics

            For Each tableName As String In tableStats.Keys

                Dim ts As DbSnapshotInitializationTableStatistics = tableStats(tableName)

                Console.WriteLine(vbTab & "Table Name: " & tableName)

                Console.WriteLine(vbTab & "Total Rows: " & ts.TotalRows)

                Console.WriteLine(vbTab & "Rows Intialized: " & ts.RowsInitialized)

                Console.WriteLine(vbTab & "Start Time: " & ts.StartTime)

                Console.WriteLine(vbTab & "End Time: " & ts.EndTime)

            Next

            Console.WriteLine("Snapshot initialization process completed")
            Console.WriteLine("")
        End Sub

        Public Function ConfigureDbSyncProvider(ByVal peerConnString As String) As DbSyncProvider

            Dim sampleDbProvider As New DbSyncProvider()

            Dim peerConnection As New SqlConnection(peerConnString)
            sampleDbProvider.Connection = peerConnection
            sampleDbProvider.ScopeName = "Sales"

            'Create a DbSyncAdapter object for the Customer table and associate it 
            'with the DbSyncProvider. Following the DataAdapter style in ADO.NET, 
            'DbSyncAdapter is the equivalent for synchronization. The commands that 
            'are specified for the DbSyncAdapter object call stored procedures 
            'that are created in each peer database. 
            Dim adapterCustomer As New DbSyncAdapter("Customer")


            'Specify the primary key, which Sync Framework uses 
            'to identify each row during synchronization. 
            adapterCustomer.RowIdColumns.Add("CustomerId")


            'Specify the command to select incremental changes. 
            'In this command and other commands, session variables are 
            'used to pass information at runtime. DbSyncSession.SyncMetadataOnly 
            'and SyncMinTimestamp are two of the string constants that 
            'the DbSyncSession class exposes. You could also include 
            '@sync_metadata_only and @sync_min_timestamp directly in your 
            'queries: 
            '* sync_metadata_only is used by Sync Framework as an optimization 
            ' in some queries. 
            '* The value of the sync_min_timestamp session variable is compared to 
            ' values in the sync_row_timestamp column in the tracking table to 
            ' determine which rows to select. 
            Dim chgsCustomerCmd As New SqlCommand()
            With chgsCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_SelectChanges"
                .Parameters.Add("@" & DbSyncSession.SyncMetadataOnly, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncInitialize, SqlDbType.Int)
            End With

            adapterCustomer.SelectIncrementalChangesCommand = chgsCustomerCmd

            'Specify the command to insert rows. 
            'The sync_row_count session variable is used in this command 
            'and other commands to return a count of the rows affected by an operation. 
            'A count of 0 indicates that an operation failed. 
            Dim insCustomerCmd As New SqlCommand()
            With insCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_ApplyInsert"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
                .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
                .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            adapterCustomer.InsertCommand = insCustomerCmd


            'Specify the command to update rows. 
            'The value of the sync_min_timestamp session variable is compared to 
            'values in the sync_row_timestamp column in the tracking table to 
            'determine which rows to update. 
            Dim updCustomerCmd As New SqlCommand()
            With updCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_ApplyUpdate"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
                .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
                .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
                .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
                .Parameters.Add("@" & DbSyncSession.SyncForceWrite, SqlDbType.Int)
            End With

            adapterCustomer.UpdateCommand = updCustomerCmd


            'Specify the command to delete rows. 
            'The value of the sync_min_timestamp session variable is compared to 
            'values in the sync_row_timestamp column in the tracking table to 
            'determine which rows to delete. 
            Dim delCustomerCmd As New SqlCommand()
            With delCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_ApplyDelete"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncMinTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
                .Parameters.Add("@" & DbSyncSession.SyncForceWrite, SqlDbType.Int)
            End With

            adapterCustomer.DeleteCommand = delCustomerCmd

            'Specify the command to select any conflicting rows. 
            Dim selRowCustomerCmd As New SqlCommand()
            With selRowCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_SelectRow"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
            End With

            adapterCustomer.SelectRowCommand = selRowCustomerCmd


            'Specify the command to insert metadata rows. 
            'The session variables in this command relate to columns in 
            'the tracking table. 
            Dim insMetadataCustomerCmd As New SqlCommand()
            With insMetadataCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_InsertMetadata"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncCreatePeerKey, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowIsTombstone, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            adapterCustomer.InsertMetadataCommand = insMetadataCustomerCmd


            'Specify the command to update metadata rows. 
            Dim updMetadataCustomerCmd As New SqlCommand()
            With updMetadataCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_UpdateMetadata"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncScopeLocalId, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncCreatePeerKey, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncCreatePeerTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerKey, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncUpdatePeerTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowIsTombstone, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            adapterCustomer.UpdateMetadataCommand = updMetadataCustomerCmd

            'Specify the command to delete metadata rows. 
            Dim delMetadataCustomerCmd As New SqlCommand()
            With delMetadataCustomerCmd
                .CommandType = CommandType.StoredProcedure
                .CommandText = "Sync.sp_Customer_DeleteMetadata"
                .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncRowTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            adapterCustomer.DeleteMetadataCommand = delMetadataCustomerCmd


            'Add the adapter to the provider. 
            sampleDbProvider.SyncAdapters.Add(adapterCustomer)


            ' Configure commands that relate to the provider itself rather 
            ' than the DbSyncAdapter object for each table: 
            ' * SelectNewTimestampCommand: Returns the new high watermark for 
            ' the current synchronization session. 
            ' * SelectScopeInfoCommand: Returns sync knowledge, cleanup knowledge, 
            ' and a scope version (timestamp). 
            ' * UpdateScopeInfoCommand: Sets new values for sync knowledge and cleanup knowledge. 
            ' * SelectTableMaxTimestampsCommand (optional): Returns the maximum timestamp from each base table 
            ' or tracking table, to determine whether for each table the destination already 
            ' has all of the changes from the source. If a destination table has all the changes, 
            ' SelectIncrementalChangesCommand is not called for that table. 
            ' There are additional commands related to metadata cleanup that are not 
            ' included in this application. 


            'Select a new timestamp. 
            'During each synchronization, the new value and 
            'the last value from the previous synchronization 
            'are used: the set of changes between these upper and 
            'lower bounds is synchronized. 
            Dim selectNewTimestampCommand As New SqlCommand()
            Dim newTimestampVariable As String = "@" & DbSyncSession.SyncNewTimestamp
            With selectNewTimestampCommand
                .CommandText = "SELECT " & newTimestampVariable & " = min_active_rowversion() - 1"
                .Parameters.Add(newTimestampVariable, SqlDbType.Timestamp)
                .Parameters(newTimestampVariable).Direction = ParameterDirection.Output
            End With

            sampleDbProvider.SelectNewTimestampCommand = selectNewTimestampCommand

            'Specify the command to select local replica metadata. 
            Dim selReplicaInfoCmd As New SqlCommand()
            With selReplicaInfoCmd
                .CommandType = CommandType.Text
                .CommandText = "SELECT " _
                             & "scope_id, " _
                             & "scope_local_id, " _
                             & "scope_sync_knowledge, " _
                             & "scope_tombstone_cleanup_knowledge, " _
                             & "scope_timestamp " _
                             & "FROM Sync.ScopeInfo " _
                             & "WHERE scope_name = @" + DbSyncSession.SyncScopeName
                .Parameters.Add("@" & DbSyncSession.SyncScopeName, SqlDbType.NVarChar, 100)
            End With

            sampleDbProvider.SelectScopeInfoCommand = selReplicaInfoCmd


            'Specify the command to update local replica metadata. 
            Dim updReplicaInfoCmd As New SqlCommand()
            With updReplicaInfoCmd
                .CommandType = CommandType.Text
                .CommandText = "UPDATE  Sync.ScopeInfo SET " _
                             & "scope_sync_knowledge = @" + DbSyncSession.SyncScopeKnowledge + ", " _
                             & "scope_id = @" + DbSyncSession.SyncScopeId + ", " _
                             & "scope_tombstone_cleanup_knowledge = @" + DbSyncSession.SyncScopeCleanupKnowledge + " " _
                             & "WHERE scope_name = @" + DbSyncSession.SyncScopeName + " AND " _
                             & " ( @" + DbSyncSession.SyncCheckConcurrency + " = 0 OR scope_timestamp = @" + DbSyncSession.SyncScopeTimestamp + "); " _
                             & "SET @" + DbSyncSession.SyncRowCount + " = @@rowcount"
                .Parameters.Add("@" & DbSyncSession.SyncScopeKnowledge, SqlDbType.VarBinary, 10000)
                .Parameters.Add("@" & DbSyncSession.SyncScopeCleanupKnowledge, SqlDbType.VarBinary, 10000)
                .Parameters.Add("@" & DbSyncSession.SyncScopeName, SqlDbType.NVarChar, 100)
                .Parameters.Add("@" & DbSyncSession.SyncCheckConcurrency, SqlDbType.Int)
                .Parameters.Add("@" & DbSyncSession.SyncScopeId, SqlDbType.UniqueIdentifier)
                .Parameters.Add("@" & DbSyncSession.SyncScopeTimestamp, SqlDbType.BigInt)
                .Parameters.Add("@" & DbSyncSession.SyncRowCount, SqlDbType.Int).Direction = ParameterDirection.Output
            End With

            sampleDbProvider.UpdateScopeInfoCommand = updReplicaInfoCmd


            'Return the maximum timestamp from the Customer_Tracking table. 
            'If more tables are synchronized, the query should UNION 
            'all of the results. The table name is not schema-qualified 
            'in this case because the name was not schema qualified in the 
            'DbSyncAdapter constructor. 
            Dim selTableMaxTsCmd As New SqlCommand()
            selTableMaxTsCmd.CommandType = CommandType.Text
            selTableMaxTsCmd.CommandText = "SELECT 'Customer' AS table_name, " _
                                         & "MAX(local_update_peer_timestamp) AS max_timestamp " _
                                         & "FROM Sync.Customer_Tracking"
            sampleDbProvider.SelectTableMaxTimestampsCommand = selTableMaxTsCmd

            Return sampleDbProvider
        End Function
    End Class

    'Handle the statistics that are returned by the SyncAgent. 
    Public Class SampleStats
        Public Sub DisplayStats(ByVal syncStatistics As SyncOperationStatistics, ByVal syncType As String)
            Console.WriteLine([String].Empty)
            If syncType = "initial" Then
                Console.WriteLine("****** Initial Synchronization ******")
            ElseIf syncType = "subsequent" Then
                Console.WriteLine("***** Subsequent Synchronization ****")
            End If

            Console.WriteLine("Start Time: " & syncStatistics.SyncStartTime)
            Console.WriteLine("Total Changes Uploaded: " & syncStatistics.UploadChangesTotal)
            Console.WriteLine("Total Changes Downloaded: " & syncStatistics.DownloadChangesTotal)
            Console.WriteLine("Complete Time: " & syncStatistics.SyncEndTime)
            Console.WriteLine([String].Empty)
        End Sub
    End Class
End Class

참고 항목

개념

방법: 공동 작업 동기화를 위한 서버 데이터베이스 프로비전(SQL Server 이외)
다른 ADO.NET 호환 데이터베이스 동기화