다음을 통해 공유


방법: SQL Server 변경 내용 추적 사용

이 항목에서는 SQL Server 변경 내용 추적에 대해 간략하게 설명하고 SQL Server 데이터베이스와 SQL Server Compact 데이터베이스 사이에서 양방향 동기화를 수행하는 콘솔 응용 프로그램을 보여 줍니다. 서버에서 SQL Server 2008을 실행하는 경우 SQL Server 변경 내용 추적을 사용하는 것이 좋습니다. 서버에서 다른 데이터베이스를 실행하고 있는 경우 방법: 사용자 지정 변경 내용 추적 시스템 사용을 참조하십시오.

SQL Server 변경 내용 추적 개요

이 설명서의 많은 예제에서 변경 내용 추적은 기본 테이블에 추가된 열과 트리거 집합 및 삭제 작업을 추적하는 추가 테이블을 통해 처리됩니다. 자세한 내용은 서버 데이터베이스의 변경 내용 추적를 참조하십시오. 이러한 추적 방식은 SQL Server 2008 데이터베이스 이외의 데이터베이스에서 유용하지만 다음과 같은 단점이 있습니다.

  • 서버 데이터베이스에서 스키마를 변경해야 합니다. 이는 다른 응용 프로그램에 영향을 줄 수도 있고, 처음부터 불가능할 수도 있습니다.

  • 행에 대한 각 변경 내용마다 트리거가 발생합니다. 이로 인해 성능이 저하될 수 있습니다.

  • 올바른 행 버전 및 삭제를 유지 관리하는 논리가 복잡해질 수 있습니다.

  • 서버 데이터베이스에 장기 실행 트랜잭션이 있는 경우 이러한 트랜잭션을 올바르게 처리하지 못하면 데이터 변경 내용이 동기화 중에 손실될 수 있습니다. 이로 인해 데이터가 일치하지 않을 수 있습니다.

SQL Server 변경 내용 추적을 통해 이러한 문제를 해결하고 변경 내용을 간편하게 추적할 수 있습니다. 테이블에서 변경 내용 추적이 사용되는 경우 SQL Server 데이터베이스 엔진에서는 테이블의 변경 내용에 대한 정보를 유지 관리합니다. 응용 프로그램에서는 변경 내용 추적 함수를 사용하여 변경된 행을 확인하고 변경 내용에 대한 정보를 얻습니다. SQL Server 변경 내용 추적의 주요 장점은 다음과 같습니다.

  • Sync Framework를 사용하는 오프라인 동기화 시나리오에서 트리거, 타임스탬프 열, 다른 추가 열 또는 추가 테이블을 만들 필요가 없습니다.

  • DML 작업이 발생할 때가 아니라 커밋할 때 변경 내용이 추적됩니다.

  • 함수가 테이블 및 버전 정보에 대한 증분 변경 내용을 반환합니다. 이러한 함수는 겹치고 커밋되지 않은 트랜잭션이 있을 때에도 신뢰할 수 있고 사용하기 쉬운 결과를 제공합니다.

  • 성능 오버헤드가 최소 수준입니다.

  • 변경 내용 추적 데이터가 자동으로 정리될 수 있습니다.

이 항목의 나머지 부분에서는 Sync Framework 응용 프로그램에서 SQL Server 변경 내용 추적을 사용하는 방법을 보여 줍니다. 변경 내용 추적에 대한 자세한 내용은 SQL Server 2008 온라인 설명서를 참조하십시오.

Sync Framework 오프라인 데이터베이스 공급자에 SQL Server 변경 내용 추적 사용

이 단원에서는 변경 내용 추적을 설정하는 방법과 변경 내용 추적 쿼리를 사용하여 클라이언트에 다운로드할 데이터 변경 내용을 결정하는 방법에 대해 설명합니다. 이 단원에서는 직접 만든 명령을 사용하여 서버에서 변경 내용을 선택하는 방법에 대해 설명합니다. 동기화 어댑터 작성기를 사용하여 자동으로 명령을 만드는 방법에 대한 자세한 내용은 시작: 클라이언트 및 서버 동기화을 참조하십시오.

SQL Server 변경 내용 추적 설정

변경 내용 추적은 서버 데이터베이스에 설정된 다음 추적이 필요한 각 테이블에 대해 설정됩니다. 다음 코드 예제에서는 Sync Framework 샘플 데이터베이스 중 하나에 있는 Sales.Customer 테이블에 대한 스키마와 해당 테이블에 대한 변경 내용 추적을 설정하는 데 필요한 코드를 보여 줍니다. 각 테이블에는 기본 키가 있어야 합니다. 기본 키는 모든 노드에서 고유해야 하며 다시 사용하면 안 됩니다. 행이 삭제되면 해당 행의 기본 키가 다른 행에 사용되지 않아야 합니다. 일반적으로 분산 환경에서는 ID 열을 사용하지 않는 것이 좋습니다. 기본 키에 대한 자세한 내용은 분산 환경에 대해 적절한 기본 키 선택을 참조하십시오.

다음 코드를 실행하여 지정된 변경 내용 추적 옵션에는 추적 메타데이터를 유지할 기간 및 해당 메타데이터를 자동으로 정리할지 여부가 포함됩니다. 추적 옵션에 대한 자세한 내용은 SQL Server 2008 온라인 설명서에서 "변경 내용 추적", "ALTER DATABASE" 및 "ALTER TABLE" 항목을 참조하십시오.

CREATE TABLE SyncSamplesDb_ChangeTracking.Sales.Customer(
    CustomerId uniqueidentifier NOT NULL PRIMARY KEY DEFAULT NEWID(), 
    CustomerName nvarchar(100) NOT NULL,
    SalesPerson nvarchar(100) NOT NULL,
    CustomerType nvarchar(100) NOT NULL)

ALTER DATABASE SyncSamplesDb_ChangeTracking SET ALLOW_SNAPSHOT_ISOLATION ON

ALTER DATABASE SyncSamplesDb_ChangeTracking
SET CHANGE_TRACKING = ON
(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON)

ALTER TABLE SyncSamplesDb_ChangeTracking.Sales.Customer
ENABLE CHANGE_TRACKING

참고

변경 정보를 쿼리할 때 스냅숏 트랜잭션을 사용하는 것이 좋습니다. 이렇게 하면 변경 정보의 일관성을 유지하는 데 도움이 되고 백그라운드 정리 태스크와 관련된 경합 상태를 피할 수 있습니다. 스냅숏 격리에 대한 자세한 내용은 SQL Server 2008 온라인 설명서에서 "데이터베이스 엔진의 격리 수준"을 참조하십시오.

클라이언트로 다운로드할 데이터 변경 내용 결정

변경 내용 추적을 설정하면 Sync Framework 응용 프로그램에서 변경 내용 추적 함수 및 앵커를 사용하여 다운로드할 삽입, 업데이트 및 삭제를 결정할 수 있습니다. 앵커는 동기화할 변경 내용 집합을 정의하는 데 사용되는 지점입니다. 다음 쿼리를 검토하십시오.

  • SelectIncrementalInsertsCommand 속성에 대해 지정하는 쿼리입니다. 다음 쿼리에서는 클라이언트에 적용할 증분 삽입을 서버에 있는 Sales.Customer 테이블에서 선택합니다.

    IF @sync_initialized = 0
      SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] 
      FROM Sales.Customer LEFT OUTER JOIN 
      CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT
      ON CT.[CustomerId] = Sales.Customer.[CustomerId]
    ELSE
    BEGIN
      SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType]
      FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT
      ON CT.[CustomerId] = Sales.Customer.[CustomerId]
      WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION 
      <= @sync_new_received_anchor)
    END
    

    이것이 클라이언트에 대한 첫 번째 동기화 세션인 경우(@sync_initialized = 0) 스키마와 모든 행은 Sales.Customer 기본 테이블에서 직접 선택됩니다. 이후에 동기화할 때 기본 테이블과 해당 변경 내용 추적 테이블 사이에서 내부 조인을 수행하여 새로 삽입된 행을 선택합니다. 변경 내용 추적 테이블의 메타데이터는 CHANGETABLE() 함수를 통해 노출됩니다. 이 함수는 이전 동기화에서 저장된 기본 테이블 이름과 변경 내용 추적 버전을 매개 변수로 사용합니다. SYS_CHANGE_OPERATION 열은 변경 내용 추적 테이블 행에 저장된 변경 내용 형식을 정의합니다.

    참고

    쿼리에서는 필요한 변경 내용이 추적 테이블에서 정리되었는지 여부도 확인해야 합니다. 여기에 대한 예제는 이 항목의 뒷부분에서 "클라이언트에 적용할 증분 삽입을 서버에서 선택하는 명령 지정"을 참조하십시오.

  • SelectNewAnchorCommand 속성에 대해 지정하는 쿼리입니다. 이 쿼리는 지정 시간 값을 검색합니다. 다음 쿼리에서는 change_tracking_current_version() 함수를 사용하여 새 앵커 값을 검색합니다. 이 기본 제공 SQL Server 함수는 변경 내용 추적을 통해 추적되었으며 마지막으로 커밋된 트랜잭션에 연결된 버전 정수를 반환합니다.

    SELECT @sync_new_received_anchor = change_tracking_current_version()
    

    이 정수 값은 클라이언트 데이터베이스에 저장되며 변경 내용을 동기화하는 명령에서 사용됩니다. 각 동기화 세션 동안 이전 동기화 세션의 새 앵커 값과 마지막 앵커 값을 사용합니다. 이러한 상한과 하한 사이의 변경 내용 집합이 동기화됩니다.

응용 프로그램에서 각 클라이언트의 데이터 중 일부만 필요한 경우도 있습니다. 이러한 경우 WHERE 절에 추가 조건을 포함하여 데이터를 필터링할 수 있습니다. 자세한 내용은 방법: 행 및 열 필터링을 참조하십시오. "키가 아닌 열에 따른 필터" 단원에는 SQL Server 변경 내용 추적을 통한 필터링과 관련된 중요한 정보가 포함되어 있습니다.

동기화 프로세스에서 실행되는 쿼리

Sales.Customer 테이블이 처음으로 동기화되면 다음과 같은 프로세스가 수행됩니다.

  1. 새 앵커 명령이 실행됩니다. 명령에서 372와 같은 정수 값을 반환합니다. 이 값은 클라이언트 데이터베이스에 저장됩니다. 테이블은 아직 동기화되지 않은 상태이므로 클라이언트 데이터베이스에는 이전 동기화에서 생성된 앵커 값이 저장되어 있지 않습니다. 이 경우 Sync Framework에서 값 0을 사용합니다. Sync Framework가 실행하는 쿼리는 다음과 같습니다.

    exec sp_executesql N'IF @sync_initialized = 0 SELECT 
    Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], 
    [CustomerType] FROM Sales.Customer LEFT OUTER JOIN 
    CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT 
    ON CT.[CustomerId] = Sales.Customer.[CustomerId] ELSE  BEGIN SELECT 
    Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], 
    [CustomerType] FROM Sales.Customer JOIN CHANGETABLE(CHANGES 
    Sales.Customer, @sync_last_received_anchor) CT ON CT.[CustomerId] = 
    Sales.Customer.[CustomerId] WHERE (CT.SYS_CHANGE_OPERATION = ''I'' 
    AND CT.SYS_CHANGE_CREATION_VERSION <= @sync_new_received_anchor) 
    END', N'@sync_initialized int, @sync_last_received_anchor bigint, 
    @sync_new_received_anchor bigint', @sync_initialized=0, 
    @sync_last_received_anchor=0, @sync_new_received_anchor=372
    
  2. 두 번째 동기화 세션 동안 새 앵커 명령이 실행됩니다. 마지막 세션 이후에 행이 삽입되었으므로 명령이 값 375를 반환합니다. 테이블은 이전에 동기화되었으므로 Sync Framework는 이전 동기화에서 클라이언트 데이터베이스에 저장된 앵커 값인 372를 검색할 수 있습니다. 다음과 같은 쿼리가 실행되며 두 앵커 값 사이에 삽입된 행만 테이블에서 다운로드됩니다.

    exec sp_executesql N'IF @sync_initialized = 0 SELECT 
    Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], 
    [CustomerType] FROM Sales.Customer LEFT OUTER JOIN 
    CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT 
    ON CT.[CustomerId] = Sales.Customer.[CustomerId] ELSE  BEGIN SELECT 
    Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], 
    [CustomerType] FROM Sales.Customer JOIN CHANGETABLE(CHANGES 
    Sales.Customer, @sync_last_received_anchor) CT ON CT.[CustomerId] = 
    Sales.Customer.[CustomerId] WHERE (CT.SYS_CHANGE_OPERATION = ''I'' 
    AND CT.SYS_CHANGE_CREATION_VERSION <= @sync_new_received_anchor) 
    END', N'@sync_initialized int, @sync_last_received_anchor bigint, 
    @sync_new_received_anchor bigint', @sync_initialized=1, 
    @sync_last_received_anchor=372, @sync_new_received_anchor=375
    

업데이트 및 삭제 명령의 예는 이 항목의 뒷부분에 나오는 전체 코드 예제를 참조하십시오.

데이터를 변경한 클라이언트 확인

데이터를 변경한 클라이언트를 확인해야 하는 이유는 크게 두 가지입니다.

  • 업로드 전용 및 양방향 동기화에서 충돌 검색 및 해결을 지원하려는 경우

    서버 및 클라이언트와 둘 이상의 클라이언트가 지정된 행을 변경할 수 있는 경우 변경을 수행한 항목을 확인해야 할 수 있습니다. 이 정보를 확인하면 예를 들어 특정 변경 내용을 보다 우선적으로 적용하는 코드를 작성할 수 있습니다. 이 정보가 없으면 행에 대한 마지막 변경 내용이 유지됩니다.

  • 양방향 동기화 중에 클라이언트에 변경 내용을 다시 에코하지 않도록 하려는 경우

    Sync Framework는 변경 내용을 먼저 서버로 업로드한 다음 클라이언트로 다운로드합니다. 변경을 수행한 클라이언트의 ID를 추적하지 않는 경우 변경 내용이 동일한 동기화 세션 동안 서버로 업로드된 후에 클라이언트로 다시 다운로드됩니다. 이와 같이 변경 내용을 에코해야 하는 경우도 있지만 그렇지 않은 경우도 있습니다.

변경 내용 추적에서 제공하는 메커니즘을 사용하면 행이 변경될 때 변경 정보와 응용 프로그램 데이터를 함께 저장할 수 있습니다. 이러한 응용 프로그램 데이터를 사용하여 변경을 수행 중인 클라이언트를 식별할 수 있습니다. 그런 다음 변경 내용을 쿼리할 때 변경을 수행한 클라이언트의 ID를 반환할 수 있습니다.

SYS_CHANGE_CONTEXT 열을 ClientId 속성과 함께 사용하면 각 삽입, 업데이트 또는 삭제를 수행한 클라이언트를 확인할 수 있습니다. 스냅숏 동기화 이외의 다른 방법을 사용하여 테이블을 처음으로 동기화하면 Sync Framework는 클라이언트를 식별하는 GUID 값을 해당 클라이언트에 저장합니다. 이 ID는 각 SyncAdapter 개체에 대한 명령에 사용할 수 있도록 DbServerSyncProvider에 전달됩니다. ClientId 속성, @sync_client_id@sync_client_id_binary 세션 변수를 통해 ID 값을 사용할 수 있습니다. 다음 Transact-SQL 쿼리를 참조하십시오.

IF @sync_initialized = 0
  SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] 
  FROM Sales.Customer LEFT OUTER JOIN 
  CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT
  ON CT.[CustomerId] = Sales.Customer.[CustomerId]
  WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)
ELSE
BEGIN
  SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType]
  FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT
  ON CT.[CustomerId] = Sales.Customer.[CustomerId]
  WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION 
<= @sync_new_received_anchor 
  AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary));

이 쿼리는 서버에 대해 수행한 삽입 작업을 추적하는 앞서 나온 쿼리와 비슷합니다. 각 WHERE 절의 추가 명령문은 현재 동기화 중인 클라이언트에서 수행되지 않은 삽입만 다운로드하도록 지정합니다. 또한 응용 프로그램에서는 Sync Framework를 통해 GUID 값 대신 서버의 정수를 사용하여 클라이언트를 식별할 수 있습니다. 자세한 내용은 방법: 세션 변수 사용을 참조하십시오.

서버에 적용된 데이터 변경 내용을 만든 클라이언트를 추적하려면 WITH CHANGE_TRACKING_CONTEXT 절을 사용합니다. INSERT, UPDATE 또는 DELETE 문을 실행하기 전에 CHANGE_TRACKING_CONTEXT@sync_client_id 또는 @sync_client_id_binary 세션 변수의 값으로 설정합니다. 이 정보는 변경 내용 추적 테이블에 저장되므로 변경 작업이 수행된 컨텍스트를 응용 프로그램이 추적할 수 있습니다. Sync Framework의 경우 이는 일반적으로 클라이언트 ID이지만 varbinary(128) 열에 맞는 모든 값을 저장할 수 있습니다.

WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary)
INSERT INTO Sales.Customer (CustomerId, CustomerName, SalesPerson,
 CustomerType)
VALUES (@CustomerId, @CustomerName, @SalesPerson, @CustomerType)
SET @sync_row_count = @@rowcount

예제 응용 프로그램 이해 및 실행

이 단원에는 동기화를 구성하고 수행하는 데 필요한 응용 프로그램 코드가 포함되어 있습니다. 예제 코드만 읽어 보아도 이러한 클래스에 대한 내용을 확인할 수 있습니다. 그러나 예제 코드를 직접 실행하여 작동 방식을 확인하는 것이 보다 효과적입니다. 코드를 실행하기 전에 다음을 설치해야 합니다.

  • Sync Framework

    응용 프로그램에 Microsoft.Synchronization.Data.dll, Microsoft.Synchronization.dll, Microsoft.Synchronization.Data.Server.dll 및 Microsoft.Synchronization.Data.SqlServerCe.dll에 대한 참조가 필요합니다.

  • SQL Server 2008

    예제 코드에서는 연결 문자열에 localhost를 사용합니다. 원격 서버를 사용하려면 localhost를 해당 서버 이름으로 변경하십시오.

  • Sync Framework 샘플 데이터베이스입니다. 자세한 내용은 데이터베이스 공급자용 설치 스크립트 방법 항목을 참조하십시오.

클라이언트 및 서버 동기화에 대한 아키텍처 및 클래스 항목을 읽어 본 경우 응용 프로그램에 사용되는 주 클래스를 이해하고 있어야 합니다. 응용 프로그램은 다음과 같은 클래스로 구성되어 있습니다.

  • SampleSyncAgent. 이 클래스는 SyncAgent에서 파생됩니다.

  • SampleServerSyncProvider. 이 클래스는 DbServerSyncProvider에서 파생되며 SyncAdapter 및 변경 내용 추적 테이블을 쿼리하는 명령 집합을 포함합니다.

  • SampleClientSyncProvider. 이 클래스는 SqlCeClientSyncProvider에서 파생되며 SyncTable을 포함합니다.

  • SampleStats. 이 클래스는 SyncAgent에서 반환하는 통계를 사용합니다.

  • Program. 이 클래스는 동기화를 설정하고 Utility 클래스의 메서드를 호출합니다.

  • Utility. 이 클래스는 연결 문자열 정보 보유 및 서버 데이터베이스와 클라이언트 데이터베이스 변경과 같이 동기화와 직접 관련되지 않은 모든 기능을 처리합니다. 자세한 내용은 데이터베이스 공급자용 유틸리티 클래스 방법 항목을 참조하십시오.

API의 주요 요소

전체 코드 예제를 살펴보기 전에 다음 예제를 검토하는 것이 좋습니다. 이 예제에서는 이 응용 프로그램에 사용되는 몇 가지 주요 API 부문을 보여 줍니다. 표시된 모든 예제 코드는 SampleServerSyncProvider 클래스에 들어 있습니다. 전체 코드 예제에는 이 단원에 나와 있는 명령 이외에 서버에 삽입을 적용하는 명령 및 삭제를 선택하고 적용하는 명령도 들어 있습니다.

첫 번째 예제는 DbServerSyncProvider의 속성인 SelectNewAnchorCommand에 직접 적용됩니다. 다른 예제는 Sales.Customer 테이블의 SyncAdapter 개체에 적용됩니다.

서버에서 새 앵커 값 검색

다음 코드 예제에서는 서버에서 새 앵커 값을 검색하는 명령을 지정합니다. SyncSession 클래스에는 동기화 명령에 사용할 수 있는 몇 가지 문자열 상수가 포함되어 있으며, 그 중 하나가 SyncNewReceivedAnchor입니다. 리터럴 @sync_new_received_anchor를 쿼리에서 직접 사용할 수도 있습니다.

SqlCommand selectNewAnchorCommand = new SqlCommand();
string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
selectNewAnchorCommand.CommandText =
    "SELECT " + newAnchorVariable + " = change_tracking_current_version()";
selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.BigInt);
selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
selectNewAnchorCommand.Connection = serverConn;
this.SelectNewAnchorCommand = selectNewAnchorCommand;
Dim selectNewAnchorCommand As New SqlCommand()
Dim newAnchorVariable As String = "@" + SyncSession.SyncNewReceivedAnchor
With selectNewAnchorCommand
    .CommandText = _
        "SELECT " + newAnchorVariable + " = change_tracking_current_version()"
    .Parameters.Add(newAnchorVariable, SqlDbType.BigInt)
    .Parameters(newAnchorVariable).Direction = ParameterDirection.Output
    .Connection = serverConn
End With
Me.SelectNewAnchorCommand = selectNewAnchorCommand

클라이언트에 적용할 증분 삽입을 서버에서 선택하는 명령 지정

다음 코드 예제에서는 클라이언트에 적용할 증분 삽입을 서버에서 선택하는 명령을 지정합니다. 증분 변경 내용에 대한 모든 쿼리에서는 필요한 변경 내용이 변경 내용 추적 테이블에서 정리되었는지 여부를 확인해야 합니다. 다음 절에서 이러한 확인이 시작되며, 변경 내용이 정리된 경우 오류가 발생합니다.

IF CHANGE_TRACKING_MIN_VALID_VERSION (object_id (@sync_table_name)) > @sync_last_received_anchor

SqlCommand customerIncrInserts = new SqlCommand();
customerIncrInserts.CommandText =
    "IF @sync_initialized = 0 " +
        "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
        "FROM Sales.Customer LEFT OUTER JOIN " +
        "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
        "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
        "WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary) " +
    "ELSE  " +
    "BEGIN " +
        "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
        "FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
        "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
        "WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION " +
        "<= @sync_new_received_anchor " +
        "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
        "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
        "> @sync_last_received_anchor " +
        "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
        "To recover from this error, the client must reinitialize its local database and try again' " +
        ",16,3,@sync_table_name)  " +
    "END";
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
customerIncrInserts.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
customerIncrInserts.Connection = serverConn;
customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts;
Dim customerIncrInserts As New SqlCommand()
With customerIncrInserts
    .CommandText = _
        "IF @sync_initialized = 0 " _
            & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
            & "FROM Sales.Customer LEFT OUTER JOIN " _
            & "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
            & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
            & "WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary) " _
        & "ELSE  " _
        & "BEGIN " _
            & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
            & "FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
            & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
            & "WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION " _
            & "<= @sync_new_received_anchor " _
            & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
            & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
            & "> @sync_last_received_anchor " _
            & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
            & "To recover from this error, the client must reinitialize its local database and try again' " _
            & ",16,3,@sync_table_name)  " _
        & "END"
    .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
    .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
    .Connection = serverConn
End With
customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts

클라이언트에 적용할 증분 업데이트를 서버에서 선택하는 명령 지정

다음 코드 예제에서는 클라이언트에 적용할 증분 업데이트를 서버에서 선택하는 명령을 지정합니다.

SqlCommand customerIncrUpdates = new SqlCommand();
customerIncrUpdates.CommandText =
    "IF @sync_initialized > 0  " +
    "BEGIN " +
        "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
        "FROM Sales.Customer JOIN " +
        "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
        "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
        "WHERE (CT.SYS_CHANGE_OPERATION = 'U' AND CT.SYS_CHANGE_VERSION " +
        "<= @sync_new_received_anchor " +
        "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
        "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
        "> @sync_last_received_anchor " +
        "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
        "To recover from this error, the client must reinitialize its local database and try again'" +
        ",16,3,@sync_table_name)  " +
    "END";
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
customerIncrUpdates.Connection = serverConn;
customerSyncAdapter.SelectIncrementalUpdatesCommand = customerIncrUpdates;
Dim customerIncrUpdates As New SqlCommand()
With customerIncrUpdates
    .CommandText = _
          "IF @sync_initialized > 0  " _
        & "BEGIN " _
            & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
            & "FROM Sales.Customer JOIN " _
            & "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
            & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
            & "WHERE (CT.SYS_CHANGE_OPERATION = 'U' AND CT.SYS_CHANGE_VERSION " _
            & "<= @sync_new_received_anchor " _
            & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
            & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
            & "> @sync_last_received_anchor " _
            & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
            & "To recover from this error, the client must reinitialize its local database and try again'" _
            & ",16,3,@sync_table_name)  " _
        & "END"
    .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
    .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
    .Connection = serverConn
End With
customerSyncAdapter.SelectIncrementalUpdatesCommand = customerIncrUpdates

클라이언트의 증분 업데이트를 서버에 적용하는 명령 지정

다음 코드 예제에서 UPDATE 문은 기본 테이블을 업데이트하고 영향을 받은 행 수를 반환합니다. 행 개수가 0이면 오류 또는 충돌이 발생합니다. 자세한 내용은 방법: 데이터 충돌 및 오류 처리를 참조하십시오.

SqlCommand customerUpdates = new SqlCommand();
customerUpdates.CommandText =
    ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " +
    "UPDATE Sales.Customer " +
    "SET [CustomerName] = @CustomerName, [SalesPerson] = @SalesPerson, [CustomerType] = @CustomerType " +
    "FROM Sales.Customer  " +
    "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
    "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
    "WHERE (@sync_force_write = 1 " +
    "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " +
    "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " +
    "SET @sync_row_count = @@rowcount; " +
    "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " +
        "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
        "To recover from this error, the client must reinitialize its local database and try again'" +
        ",16,3,@sync_table_name)";
customerUpdates.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary); 
customerUpdates.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
customerUpdates.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
customerUpdates.Parameters.Add("@CustomerType", SqlDbType.NVarChar);            
customerUpdates.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
customerUpdates.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
customerUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
customerUpdates.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
customerUpdates.Parameters["@" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;
customerUpdates.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
customerUpdates.Connection = serverConn;
customerSyncAdapter.UpdateCommand = customerUpdates;
Dim customerUpdates As New SqlCommand()
With customerUpdates
    .CommandText = _
          ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " _
        & "UPDATE Sales.Customer " _
        & "SET [CustomerName] = @CustomerName, [SalesPerson] = @SalesPerson, [CustomerType] = @CustomerType " _
        & "FROM Sales.Customer  " _
        & "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
        & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
        & "WHERE (@sync_force_write = 1 " _
        & "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " _
        & "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " _
        & "SET @sync_row_count = @@rowcount; " _
        & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " _
            & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
            & "To recover from this error, the client must reinitialize its local database and try again'" _
            & ",16,3,@sync_table_name)"
    .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
    .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
    .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
    .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
    .Parameters("@" + SyncSession.SyncRowCount).Direction = ParameterDirection.Output
    .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
    .Connection = serverConn
End With
customerSyncAdapter.UpdateCommand = customerUpdates

충돌 행 선택

다음 명령은 행이 여전히 기본 테이블에 있는 경우 서버 데이터베이스에서 충돌 행을 선택합니다.

SqlCommand customerUpdateConflicts = new SqlCommand();
customerUpdateConflicts.CommandText =
    "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType], " +
    "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " +
    "FROM Sales.Customer JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
    "ON CT.[CustomerId] = Sales.Customer.[CustomerId]";
customerUpdateConflicts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
customerUpdateConflicts.Connection = serverConn;
customerSyncAdapter.SelectConflictUpdatedRowsCommand = customerUpdateConflicts;
Dim customerUpdateConflicts As New SqlCommand()
With customerUpdateConflicts
    .CommandText = _
          "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType], " _
        & "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " _
        & "FROM Sales.Customer JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
        & "ON CT.[CustomerId] = Sales.Customer.[CustomerId]"
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Connection = serverConn
End With
customerSyncAdapter.SelectConflictUpdatedRowsCommand = customerUpdateConflicts

다음 명령은 행이 기본 테이블에서 삭제된 경우 서버 데이터베이스에서 충돌 행을 선택합니다.

SqlCommand customerDeleteConflicts = new SqlCommand();
customerDeleteConflicts.CommandText =
    "SELECT CT.[CustomerId], " +
    "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " +
    "FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
    "WHERE (CT.[CustomerId] = @CustomerId AND CT.SYS_CHANGE_OPERATION = 'D')";
customerDeleteConflicts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
customerDeleteConflicts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
customerDeleteConflicts.Connection = serverConn;
customerSyncAdapter.SelectConflictDeletedRowsCommand = customerDeleteConflicts;
Dim customerDeleteConflicts As New SqlCommand()
With customerDeleteConflicts
    .CommandText = _
          "SELECT CT.[CustomerId], " _
        & "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " _
        & "FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
        & "WHERE (CT.[CustomerId] = @CustomerId AND CT.SYS_CHANGE_OPERATION = 'D')"
    .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
    .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
    .Connection = serverConn
End With
customerSyncAdapter.SelectConflictDeletedRowsCommand = customerDeleteConflicts

데이터 충돌을 처리하는 방법에 대한 자세한 내용은 방법: 데이터 충돌 및 오류 처리를 참조하십시오.

전체 코드 예제

다음의 전체 코드 예제에는 위에서 설명한 코드 예제와 동기화를 수행하는 데 필요한 추가 코드가 포함되어 있습니다.

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

            //Request a password for the client database, and 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);

            //Specify which server and database to connect to.
            Utility.SetServerAndDb_DbServerSync("localhost", "SyncSamplesDb_ChangeTracking");
            
            //Initial synchronization. Instantiate the SyncAgent
            //and call Synchronize.
            SampleSyncAgent sampleSyncAgent = new SampleSyncAgent();
            SyncStatistics syncStatistics = sampleSyncAgent.Synchronize();
            sampleStats.DisplayStats(syncStatistics, "initial");

            //Make changes on the server and client.
            Utility.MakeDataChangesOnServer("Customer");
            Utility.MakeDataChangesOnClient("Customer");          

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

            //Make conflicting changes on the server and client.
            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
            //Bidirectional, and that an existing table should be dropped.
            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 BigInt value
            //from the change tracking table.
            //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 + " = change_tracking_current_version()";
            selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.BigInt);
            selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
            selectNewAnchorCommand.Connection = serverConn;
            this.SelectNewAnchorCommand = selectNewAnchorCommand;
            
            //Create a SyncAdapter for the Customer table, and then define
            //the commands to synchronize changes:
            //* SelectIncrementalInsertsCommand, SelectIncrementalUpdatesCommand,
            //  and SelectIncrementalDeletesCommand are used to select changes
            //  from the server that the client provider then applies to the client.
            //* InsertCommand, UpdateCommand, and DeleteCommand are used to apply
            //  to the server the changes that the client provider has selected
            //  from the client.
            //* SelectConflictUpdatedRowsCommand SelectConflictDeletedRowsCommand
            //  are used to detect if there are conflicts on the server during
            //  synchronization.
            //The commands reference the change tracking table that is configured
            //for the Customer table.

            //Create the SyncAdapter.
            SyncAdapter customerSyncAdapter = new SyncAdapter("Customer");            
            
            //Select inserts from the server.
            SqlCommand customerIncrInserts = new SqlCommand();
            customerIncrInserts.CommandText =
                "IF @sync_initialized = 0 " +
                    "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
                    "FROM Sales.Customer LEFT OUTER JOIN " +
                    "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                    "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                    "WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary) " +
                "ELSE  " +
                "BEGIN " +
                    "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
                    "FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                    "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                    "WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION " +
                    "<= @sync_new_received_anchor " +
                    "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
                    "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
                    "> @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again' " +
                    ",16,3,@sync_table_name)  " +
                "END";
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
            customerIncrInserts.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerIncrInserts.Connection = serverConn;
            customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts;

            //Apply inserts to the server.
            SqlCommand customerInserts = new SqlCommand();
            customerInserts.CommandText =
                ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " +
                "INSERT INTO Sales.Customer ([CustomerId], [CustomerName], [SalesPerson], [CustomerType]) " +
                "VALUES (@CustomerId, @CustomerName, @SalesPerson, @CustomerType) " +
                "SET @sync_row_count = @@rowcount; " +
                "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)";
            customerInserts.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerInserts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerInserts.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
            customerInserts.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            customerInserts.Parameters.Add("@CustomerType", SqlDbType.NVarChar);
            customerInserts.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            customerInserts.Parameters["@" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;
            customerInserts.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerInserts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerInserts.Connection = serverConn;
            customerSyncAdapter.InsertCommand = customerInserts;
                                    
            //Select updates from the server.
            SqlCommand customerIncrUpdates = new SqlCommand();
            customerIncrUpdates.CommandText =
                "IF @sync_initialized > 0  " +
                "BEGIN " +
                    "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " +
                    "FROM Sales.Customer JOIN " +
                    "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                    "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                    "WHERE (CT.SYS_CHANGE_OPERATION = 'U' AND CT.SYS_CHANGE_VERSION " +
                    "<= @sync_new_received_anchor " +
                    "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
                    "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
                    "> @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)  " +
                "END";
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerIncrUpdates.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerIncrUpdates.Connection = serverConn;
            customerSyncAdapter.SelectIncrementalUpdatesCommand = customerIncrUpdates;
            
            //Apply updates to the server.
            SqlCommand customerUpdates = new SqlCommand();
            customerUpdates.CommandText =
                ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " +
                "UPDATE Sales.Customer " +
                "SET [CustomerName] = @CustomerName, [SalesPerson] = @SalesPerson, [CustomerType] = @CustomerType " +
                "FROM Sales.Customer  " +
                "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
                "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                "WHERE (@sync_force_write = 1 " +
                "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " +
                "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " +
                "SET @sync_row_count = @@rowcount; " +
                "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)";
            customerUpdates.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary); 
            customerUpdates.Parameters.Add("@CustomerName", SqlDbType.NVarChar);
            customerUpdates.Parameters.Add("@SalesPerson", SqlDbType.NVarChar);
            customerUpdates.Parameters.Add("@CustomerType", SqlDbType.NVarChar);            
            customerUpdates.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerUpdates.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
            customerUpdates.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerUpdates.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            customerUpdates.Parameters["@" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;
            customerUpdates.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerUpdates.Connection = serverConn;
            customerSyncAdapter.UpdateCommand = customerUpdates;

            //Select deletes from the server.
            SqlCommand customerIncrDeletes = new SqlCommand();
            customerIncrDeletes.CommandText =
                "IF @sync_initialized > 0  " +
                "BEGIN " +
                    "SELECT CT.[CustomerId] FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                    "WHERE (CT.SYS_CHANGE_OPERATION = 'D' AND CT.SYS_CHANGE_VERSION " +
                    "<= @sync_new_received_anchor " +
                    "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " +
                    "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " +
                    "> @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)  " +
                "END";
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerIncrDeletes.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerIncrDeletes.Connection = serverConn;
            customerSyncAdapter.SelectIncrementalDeletesCommand = customerIncrDeletes;

            //Apply deletes to the server.            
            SqlCommand customerDeletes = new SqlCommand();
            customerDeletes.CommandText =
                ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " +
                "DELETE Sales.Customer FROM Sales.Customer " +
                "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
                "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " +
                "WHERE (@sync_force_write = 1 " +
                "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " +
                "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " +
                "SET @sync_row_count = @@rowcount; " +
                "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " +
                    "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " +
                    "To recover from this error, the client must reinitialize its local database and try again'" +
                    ",16,3,@sync_table_name)";
            customerDeletes.Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary);
            customerDeletes.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerDeletes.Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit);
            customerDeletes.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);           
            customerDeletes.Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int);
            customerDeletes.Parameters["@" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;
            customerDeletes.Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar);
            customerDeletes.Connection = serverConn;
            customerSyncAdapter.DeleteCommand = customerDeletes;
           
            //This command is used if @sync_row_count returns
            //0 when changes are applied to the server.
            SqlCommand customerUpdateConflicts = new SqlCommand();
            customerUpdateConflicts.CommandText =
                "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType], " +
                "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " +
                "FROM Sales.Customer JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " +
                "ON CT.[CustomerId] = Sales.Customer.[CustomerId]";
            customerUpdateConflicts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerUpdateConflicts.Connection = serverConn;
            customerSyncAdapter.SelectConflictUpdatedRowsCommand = customerUpdateConflicts;

            //This command is used if the server provider cannot find
            //a row in the base table.
            SqlCommand customerDeleteConflicts = new SqlCommand();
            customerDeleteConflicts.CommandText =
                "SELECT CT.[CustomerId], " +
                "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " +
                "FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " +
                "WHERE (CT.[CustomerId] = @CustomerId AND CT.SYS_CHANGE_OPERATION = 'D')";
            customerDeleteConflicts.Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt);
            customerDeleteConflicts.Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier);
            customerDeleteConflicts.Connection = serverConn;
            customerSyncAdapter.SelectConflictDeletedRowsCommand = customerDeleteConflicts;


            //Add the SyncAdapter to the server synchronization provider.
            this.SyncAdapters.Add(customerSyncAdapter);

        }
    }

    //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 here we use this class to handle client 
    //provider events.
    public class SampleClientSyncProvider : SqlCeClientSyncProvider
    {
            
        public SampleClientSyncProvider()
        {
            //Specify a connection string for the sample client database.
            Utility util = new Utility();
            this.ConnectionString = Utility.ConnStr_SqlCeClientSync;

            //We use the CreatingSchema event to change the schema
            //by using the API. We use the SchemaCreated event to 
            //change the schema by using SQL.
            this.CreatingSchema +=new EventHandler<CreatingSchemaEventArgs>(SampleClientSyncProvider_CreatingSchema);
            this.SchemaCreated +=new EventHandler<SchemaCreatedEventArgs>(SampleClientSyncProvider_SchemaCreated);
        }

        private void SampleClientSyncProvider_CreatingSchema(object sender, CreatingSchemaEventArgs e)
        {
            //Set the RowGuid property because it is not copied
            //to the client by default. This is also a good time
            //to specify literal defaults with .Columns[ColName].DefaultValue;
            //but we will specify defaults like NEWID() by calling
            //ALTER TABLE after the table is created.
            Console.Write("Creating schema for " + e.Table.TableName + " | ");                        
            e.Schema.Tables["Customer"].Columns["CustomerId"].RowGuid = true;
        }

        private void SampleClientSyncProvider_SchemaCreated(object sender, SchemaCreatedEventArgs e)
        {        
            //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 util = new Utility();
            Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, e.Table.TableName);
            Console.WriteLine("Schema created for " + e.Table.TableName);
        }
    }

    //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 Uploaded: " + syncStatistics.TotalChangesUploaded);
            Console.WriteLine("Total Changes Downloaded: " + syncStatistics.TotalChangesDownloaded);
            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()

        'Request a password for the client database, and 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)

        'Specify which server and database to connect to.
        Utility.SetServerAndDb_DbServerSync("localhost", "SyncSamplesDb_ChangeTracking")

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

        'Make changes on the server and client.
        Utility.MakeDataChangesOnServer("Customer")
        Utility.MakeDataChangesOnClient("Customer")

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

        'Make conflicting changes on the server and client.
        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
        'Bidirectional, and that an existing table should be dropped.
        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 BigInt value
        'from the change tracking table.
        '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
        With selectNewAnchorCommand
            .CommandText = _
                "SELECT " + newAnchorVariable + " = change_tracking_current_version()"
            .Parameters.Add(newAnchorVariable, SqlDbType.BigInt)
            .Parameters(newAnchorVariable).Direction = ParameterDirection.Output
            .Connection = serverConn
        End With
        Me.SelectNewAnchorCommand = selectNewAnchorCommand

        'Create a SyncAdapter for the Customer table, and then define
        'the commands to synchronize changes:
        '* SelectIncrementalInsertsCommand, SelectIncrementalUpdatesCommand,
        '  and SelectIncrementalDeletesCommand are used to select changes
        '  from the server that the client provider then applies to the client.
        '* InsertCommand, UpdateCommand, and DeleteCommand are used to apply
        '  to the server the changes that the client provider has selected
        '  from the client.
        '* SelectConflictUpdatedRowsCommand SelectConflictDeletedRowsCommand
        '  are used to detect if there are conflicts on the server during
        '  synchronization.
        'The commands reference the change tracking table that is configured
        'for the Customer table.
        'Create the SyncAdapter.
        Dim customerSyncAdapter As New SyncAdapter("Customer")

        'Select inserts from the server.
        Dim customerIncrInserts As New SqlCommand()
        With customerIncrInserts
            .CommandText = _
                "IF @sync_initialized = 0 " _
                    & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
                    & "FROM Sales.Customer LEFT OUTER JOIN " _
                    & "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                    & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                    & "WHERE (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary) " _
                & "ELSE  " _
                & "BEGIN " _
                    & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
                    & "FROM Sales.Customer JOIN CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                    & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                    & "WHERE (CT.SYS_CHANGE_OPERATION = 'I' AND CT.SYS_CHANGE_CREATION_VERSION " _
                    & "<= @sync_new_received_anchor " _
                    & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
                    & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
                    & "> @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again' " _
                    & ",16,3,@sync_table_name)  " _
                & "END"
            .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectIncrementalInsertsCommand = customerIncrInserts
        'Apply inserts to the server.
        Dim customerInserts As New SqlCommand()
        With customerInserts
            .CommandText = _
                  ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " _
                & "INSERT INTO Sales.Customer ([CustomerId], [CustomerName], [SalesPerson], [CustomerType]) " _
                & "VALUES (@CustomerId, @CustomerName, @SalesPerson, @CustomerType) " _
                & "SET @sync_row_count = @@rowcount; " _
                & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)"
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
            .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
            .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Parameters("@" + SyncSession.SyncRowCount).Direction = ParameterDirection.Output
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Connection = serverConn
        End With
        customerSyncAdapter.InsertCommand = customerInserts

        'Select updates from the server.
        Dim customerIncrUpdates As New SqlCommand()
        With customerIncrUpdates
            .CommandText = _
                  "IF @sync_initialized > 0  " _
                & "BEGIN " _
                    & "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType] " _
                    & "FROM Sales.Customer JOIN " _
                    & "CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                    & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                    & "WHERE (CT.SYS_CHANGE_OPERATION = 'U' AND CT.SYS_CHANGE_VERSION " _
                    & "<= @sync_new_received_anchor " _
                    & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
                    & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
                    & "> @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)  " _
                & "END"
            .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectIncrementalUpdatesCommand = customerIncrUpdates
        'Apply updates to the server.
        Dim customerUpdates As New SqlCommand()
        With customerUpdates
            .CommandText = _
                  ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " _
                & "UPDATE Sales.Customer " _
                & "SET [CustomerName] = @CustomerName, [SalesPerson] = @SalesPerson, [CustomerType] = @CustomerType " _
                & "FROM Sales.Customer  " _
                & "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
                & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                & "WHERE (@sync_force_write = 1 " _
                & "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " _
                & "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " _
                & "SET @sync_row_count = @@rowcount; " _
                & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)"
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@CustomerName", SqlDbType.NVarChar)
            .Parameters.Add("@SalesPerson", SqlDbType.NVarChar)
            .Parameters.Add("@CustomerType", SqlDbType.NVarChar)
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Parameters("@" + SyncSession.SyncRowCount).Direction = ParameterDirection.Output
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.UpdateCommand = customerUpdates
        'Select deletes from the server.
        Dim customerIncrDeletes As New SqlCommand()
        With customerIncrDeletes
            .CommandText = _
                  "IF @sync_initialized > 0  " _
                & "BEGIN " _
                    & "SELECT CT.[CustomerId] FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                    & "WHERE (CT.SYS_CHANGE_OPERATION = 'D' AND CT.SYS_CHANGE_VERSION " _
                    & "<= @sync_new_received_anchor " _
                    & "AND (CT.SYS_CHANGE_CONTEXT IS NULL OR CT.SYS_CHANGE_CONTEXT <> @sync_client_id_binary)); " _
                    & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) " _
                    & "> @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)  " _
                & "END"
            .Parameters.Add("@" + SyncSession.SyncInitialized, SqlDbType.Int)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncNewReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectIncrementalDeletesCommand = customerIncrDeletes

        'Apply deletes to the server.            
        Dim customerDeletes As New SqlCommand()
        With customerDeletes
            .CommandText = _
                  ";WITH CHANGE_TRACKING_CONTEXT (@sync_client_id_binary) " _
                & "DELETE Sales.Customer FROM Sales.Customer " _
                & "JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
                & "ON CT.[CustomerId] = Sales.Customer.[CustomerId] " _
                & "WHERE (@sync_force_write = 1 " _
                & "OR CT.SYS_CHANGE_VERSION IS NULL OR CT.SYS_CHANGE_VERSION <= @sync_last_received_anchor " _
                & "OR (CT.SYS_CHANGE_CONTEXT IS NOT NULL AND CT.SYS_CHANGE_CONTEXT = @sync_client_id_binary)) " _
                & "SET @sync_row_count = @@rowcount; " _
                & "IF CHANGE_TRACKING_MIN_VALID_VERSION(object_id(@sync_table_name)) > @sync_last_received_anchor " _
                    & "RAISERROR (N'SQL Server Change Tracking has cleaned up tracking information for table ''%s''. " _
                    & "To recover from this error, the client must reinitialize its local database and try again'" _
                    & ",16,3,@sync_table_name)"
            .Parameters.Add("@" + SyncSession.SyncClientIdBinary, SqlDbType.Binary)
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Parameters.Add("@" + SyncSession.SyncForceWrite, SqlDbType.Bit)
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@" + SyncSession.SyncRowCount, SqlDbType.Int)
            .Parameters("@" + SyncSession.SyncRowCount).Direction = ParameterDirection.Output
            .Parameters.Add("@" + SyncSession.SyncTableName, SqlDbType.NVarChar)
            .Connection = serverConn
        End With
        customerSyncAdapter.DeleteCommand = customerDeletes

        'This command is used if @sync_row_count returns
        '0 when changes are applied to the server.
        Dim customerUpdateConflicts As New SqlCommand()
        With customerUpdateConflicts
            .CommandText = _
                  "SELECT Sales.Customer.[CustomerId], [CustomerName], [SalesPerson], [CustomerType], " _
                & "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " _
                & "FROM Sales.Customer JOIN CHANGETABLE(VERSION Sales.Customer, ([CustomerId]), (@CustomerId)) CT  " _
                & "ON CT.[CustomerId] = Sales.Customer.[CustomerId]"
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectConflictUpdatedRowsCommand = customerUpdateConflicts

        'This command is used if the server provider cannot find
        'a row in the base table.
        Dim customerDeleteConflicts As New SqlCommand()
        With customerDeleteConflicts
            .CommandText = _
                  "SELECT CT.[CustomerId], " _
                & "CT.SYS_CHANGE_CONTEXT, CT.SYS_CHANGE_VERSION " _
                & "FROM CHANGETABLE(CHANGES Sales.Customer, @sync_last_received_anchor) CT " _
                & "WHERE (CT.[CustomerId] = @CustomerId AND CT.SYS_CHANGE_OPERATION = 'D')"
            .Parameters.Add("@" + SyncSession.SyncLastReceivedAnchor, SqlDbType.BigInt)
            .Parameters.Add("@CustomerId", SqlDbType.UniqueIdentifier)
            .Connection = serverConn
        End With
        customerSyncAdapter.SelectConflictDeletedRowsCommand = customerDeleteConflicts

        'Add the SyncAdapter to the server synchronization provider.
        Me.SyncAdapters.Add(customerSyncAdapter)

    End Sub 'New 
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 here we use this class to handle client 
'provider events.
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

        'We use the CreatingSchema event to change the schema
        'by using the API. We use the SchemaCreated event to 
        'change the schema by using SQL.
        AddHandler Me.CreatingSchema, AddressOf SampleClientSyncProvider_CreatingSchema
        AddHandler Me.SchemaCreated, AddressOf SampleClientSyncProvider_SchemaCreated
    End Sub 'New


    Private Sub SampleClientSyncProvider_CreatingSchema(ByVal sender As Object, ByVal e As CreatingSchemaEventArgs)
        'Set the RowGuid property because it is not copied
        'to the client by default. This is also a good time
        'to specify literal defaults with .Columns[ColName].DefaultValue;
        'but we will specify defaults like NEWID() by calling
        'ALTER TABLE after the table is created.
        Console.Write("Creating schema for " + e.Table.TableName + " | ")
        e.Schema.Tables("Customer").Columns("CustomerId").RowGuid = True

    End Sub 'SampleClientSyncProvider_CreatingSchema


    Private Sub SampleClientSyncProvider_SchemaCreated(ByVal sender As Object, ByVal e As SchemaCreatedEventArgs)
        '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.
        Dim util As New Utility()
        Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, e.Table.TableName)
        Console.WriteLine("Schema created for " + e.Table.TableName)

    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 Uploaded: " & syncStatistics.TotalChangesUploaded)
        Console.WriteLine("Total Changes Downloaded: " & syncStatistics.TotalChangesDownloaded)
        Console.WriteLine("Complete Time: " + syncStatistics.SyncCompleteTime)
        Console.WriteLine(String.Empty)

    End Sub 'DisplayStats
End Class 'SampleStats

참고 항목

개념

서버 데이터베이스의 변경 내용 추적