방법: 이벤트 및 프로그램 비즈니스 논리 사용
이 항목에서는 Sync Framework에서 이벤트 및 비즈니스 논리를 사용하는 방법에 대해 설명합니다. 이 항목의 예제에서는 다음과 같은 Sync Framework 형식 및 이벤트를 중점적으로 설명합니다.
SyncAgent StateChanged 이벤트 및 SessionStateChangedEventArgs
SyncAgent SessionProgress 이벤트 및 SessionProgressEventArgs
DbServerSyncProvider ApplyingChanges 이벤트, SqlCeClientSyncProvider ApplyingChanges 이벤트 및 ApplyingChangesEventArgs
SqlCeClientSyncProvider SchemaCreated 이벤트 및 SchemaCreatedEventArgs
DbServerSyncProvider ChangesSelected 이벤트, SqlCeClientSyncProvider ChangesSelected 이벤트 및 ChangesSelectedEventArgs
DbServerSyncProvider ChangesApplied 이벤트, SqlCeClientSyncProvider ChangesApplied 이벤트 및 ChangesAppliedEventArgs
DbServerSyncProvider ApplyChangeFailed 이벤트, SqlCeClientSyncProvider ApplyChangeFailed 이벤트 및 ApplyChangeFailedEventArgs
샘플 코드를 실행하는 방법에 대한 자세한 내용은 일반적인 클라이언트 및 서버 동기화 태스크 프로그래밍의 "방법 항목의 예제 응용 프로그램"을 참조하십시오.
데이터베이스 공급자 이벤트 이해
Sync Framework에서는 동기화를 수행하는 동안 사용할 수 있는 데이터와 메타데이터를 노출하는 다양한 이벤트를 제공합니다. 예를 들어 SchemaCreatedEventArgs 개체는 클라이언트 데이터베이스에서 만든 각 테이블에 대해 SyncSchema 개체에 대한 액세스를 비롯하여 스키마를 만들 때 사용한 연결 및 트랜잭션도 제공합니다. 그러므로 클라이언트 동기화 공급자와 동일한 트랜잭션 내에서 API를 통해 스키마를 변경할 수 있습니다.
다음 예제에 나와 있는 것처럼 이벤트는 사용하기에 적절한 시간에 발생합니다.
SchemaCreated 이벤트는 각 테이블을 만든 후에 발생합니다. 이는 특정 테이블에서 스키마를 변경한 다음 다른 테이블을 만드는 경우에 유용합니다.
DbServerSyncProvider ApplyingChanges 이벤트 및 SqlCeClientSyncProvider ApplyingChanges 이벤트는 변경 내용이 각 동기화 그룹에 적용되기 전에 발생합니다. 그러므로 변경 내용이 적용되기 전에 관련 테이블 그룹에 대해 메타데이터 및 변경 내용 집합을 확인할 수 있습니다.
DbServerSyncProvider ApplyChangeFailed 이벤트 및 SqlCeClientSyncProvider ApplyChangeFailed 이벤트는 행 적용이 실패할 때마다 발생합니다. 오류 또는 데이터 충돌이 발생하면 행 적용이 실패할 수 있습니다. 이러한 이벤트를 사용하면 처리를 계속하기 전에 각 오류에 응답할 수 있습니다. 그러면 실패한 변경 작업을 다시 시도하는 횟수를 줄일 수 있습니다.
예제
이 항목의 예제 코드에서는 화면에 동기화 진행률을 표시하고, 적용된 변경 내용과 실패한 변경 내용에 대해 이벤트 관련 데이터를 기록하고, 동기화 중에 데이터를 변경하여 비즈니스 논리를 적용함으로써 Sync Framework 이벤트에 응답하는 방법을 보여 줍니다. 이 예제를 코드 예제에 포함하는 대신 샘플 코드를 실행한 다음 EventLogger
및 SampleStatsAndProgress
클래스가 각 이벤트에 대해 표시 및 기록하는 데이터를 확인하는 것이 보다 유용합니다. 그러나 샘플 코드를 실행하기 전에 다음 코드 예제를 살펴보는 것이 좋습니다.
동기화 중에 비즈니스 논리 수행
이 예제에서는 동기화 중에 데이터를 제어하는 방법을 보여 줍니다. 즉, 서버측 ApplyingChanges
이벤트를 처리하여 변경 내용 집합을 서버에서 적용하기 전에 액세스하는 방법을 보여 줍니다. 또한 클라이언트측 이벤트에 대해 논리를 도입하는 방법도 보여 줍니다. 이 경우에는 열이 업데이트됩니다. 그러나 변경 내용이 서버 또는 클라이언트에서 커밋되기 전에 인벤토리 확인 또는 응용 프로그램에 필요한 기타 비즈니스 논리를 쉽게 수행할 수 있습니다. 데이터 충돌이 발생하는 경우 이 작업을 수행하는 방법에 대한 예제는 방법: 데이터 충돌 및 오류 처리를 참조하십시오.
private void SampleServerSyncProvider_ApplyingChanges(object sender, ApplyingChangesEventArgs e)
{
DataTable customerDataTable = e.Changes.Tables["Customer"];
for (int i = 0; i < customerDataTable.Rows.Count; i++)
{
if (customerDataTable.Rows[i].RowState == DataRowState.Added
&& customerDataTable.Rows[i]["CustomerType"] == "Wholesale")
{
customerDataTable.Rows[i]["CustomerType"] = "Wholesale (from mobile sales)";
}
}
}
Private Sub SampleServerSyncProvider_ApplyingChanges(ByVal sender As Object, ByVal e As ApplyingChangesEventArgs)
Dim customerDataTable As DataTable = e.Changes.Tables("Customer")
Dim i As Integer
For i = 1 To customerDataTable.Rows.Count - 1
If customerDataTable.Rows(i).RowState = DataRowState.Added _
AndAlso customerDataTable.Rows(i)("CustomerType") = "Wholesale" Then
customerDataTable.Rows(i)("CustomerType") = "Wholesale (from mobile sales)"
End If
Next i
End Sub
전체 코드 예제
다음 전체 코드 예제를 사용하려면 데이터베이스 공급자용 유틸리티 클래스 방법 항목에 나와 있는 Utility
클래스가 필요합니다.
using System;
using System.Collections;
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 SampleStatsAndProgress class handles information from the
//SyncStatistics object that the Synchronize method returns and
//from SyncAgent events.
SampleStatsAndProgress sampleStats = new SampleStatsAndProgress();
//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);
//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 a change at the client that fails when it is
//applied at the server.
Utility.MakeFailingChangeOnClient();
//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();
//Create two SyncGroups so that changes to OrderHeader
//and OrderDetail are made in one transaction. Depending on
//application requirements, you might include Customer
//in the same group.
SyncGroup customerSyncGroup = new SyncGroup("Customer");
SyncGroup orderSyncGroup = new SyncGroup("Order");
//Add each table: specify a synchronization direction of
//Bidirectional.
SyncTable customerSyncTable = new SyncTable("Customer");
customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
customerSyncTable.SyncDirection = SyncDirection.Bidirectional;
customerSyncTable.SyncGroup = customerSyncGroup;
this.Configuration.SyncTables.Add(customerSyncTable);
SyncTable orderHeaderSyncTable = new SyncTable("OrderHeader");
orderHeaderSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
orderHeaderSyncTable.SyncDirection = SyncDirection.Bidirectional;
orderHeaderSyncTable.SyncGroup = orderSyncGroup;
this.Configuration.SyncTables.Add(orderHeaderSyncTable);
SyncTable orderDetailSyncTable = new SyncTable("OrderDetail");
orderDetailSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
orderDetailSyncTable.SyncDirection = SyncDirection.Bidirectional;
orderDetailSyncTable.SyncGroup = orderSyncGroup;
this.Configuration.SyncTables.Add(orderDetailSyncTable);
//Handle the StateChanged and SessionProgress events, and
//display information to the console.
SampleStatsAndProgress sampleStats = new SampleStatsAndProgress();
this.StateChanged += new EventHandler<SessionStateChangedEventArgs>(sampleStats.DisplaySessionProgress);
this.SessionProgress += new EventHandler<SessionProgressEventArgs>(sampleStats.DisplaySessionProgress);
}
}
//Create a class that is derived from
//Microsoft.Synchronization.Server.DbServerSyncProvider.
public class SampleServerSyncProvider : DbServerSyncProvider
{
public SampleServerSyncProvider()
{
//Create a connection to the sample server database.
Utility util = new Utility();
SqlConnection serverConn = new SqlConnection(Utility.ConnStr_DbServerSync);
this.Connection = serverConn;
//Create a command to retrieve a new anchor value from
//the server. In this case, we use a timestamp value
//that is retrieved and stored in the client database.
//During each synchronization, the new anchor value and
//the last anchor value from the previous synchronization
//are used: the set of changes between these upper and
//lower bounds is synchronized.
//
//SyncSession.SyncNewReceivedAnchor is a string constant;
//you could also use @sync_new_received_anchor directly in
//your queries.
SqlCommand selectNewAnchorCommand = new SqlCommand();
string newAnchorVariable = "@" + SyncSession.SyncNewReceivedAnchor;
selectNewAnchorCommand.CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1";
selectNewAnchorCommand.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp);
selectNewAnchorCommand.Parameters[newAnchorVariable].Direction = ParameterDirection.Output;
selectNewAnchorCommand.Connection = serverConn;
this.SelectNewAnchorCommand = selectNewAnchorCommand;
//Create SyncAdapters for each table by using the SqlSyncAdapterBuilder:
// * Specify the base table and tombstone table names.
// * Specify the columns that are used to track when
// and where changes are made.
// * Specify bidirectional synchronization.
// * Call ToSyncAdapter to create the SyncAdapter.
// * Specify a name for the SyncAdapter that matches the
// the name specified for the corresponding SyncTable.
// Do not include the schema names (Sales in this case).
//Customer table
SqlSyncAdapterBuilder customerBuilder = new SqlSyncAdapterBuilder(serverConn);
customerBuilder.TableName = "Sales.Customer";
customerBuilder.TombstoneTableName = customerBuilder.TableName + "_Tombstone";
customerBuilder.SyncDirection = SyncDirection.Bidirectional;
customerBuilder.CreationTrackingColumn = "InsertTimestamp";
customerBuilder.UpdateTrackingColumn = "UpdateTimestamp";
customerBuilder.DeletionTrackingColumn = "DeleteTimestamp";
customerBuilder.CreationOriginatorIdColumn = "InsertId";
customerBuilder.UpdateOriginatorIdColumn = "UpdateId";
customerBuilder.DeletionOriginatorIdColumn = "DeleteId";
SyncAdapter customerSyncAdapter = customerBuilder.ToSyncAdapter();
customerSyncAdapter.TableName = "Customer";
this.SyncAdapters.Add(customerSyncAdapter);
//OrderHeader table.
SqlSyncAdapterBuilder orderHeaderBuilder = new SqlSyncAdapterBuilder(serverConn);
orderHeaderBuilder.TableName = "Sales.OrderHeader";
orderHeaderBuilder.TombstoneTableName = orderHeaderBuilder.TableName + "_Tombstone";
orderHeaderBuilder.SyncDirection = SyncDirection.Bidirectional;
orderHeaderBuilder.CreationTrackingColumn = "InsertTimestamp";
orderHeaderBuilder.UpdateTrackingColumn = "UpdateTimestamp";
orderHeaderBuilder.DeletionTrackingColumn = "DeleteTimestamp";
orderHeaderBuilder.CreationOriginatorIdColumn = "InsertId";
orderHeaderBuilder.UpdateOriginatorIdColumn = "UpdateId";
orderHeaderBuilder.DeletionOriginatorIdColumn = "DeleteId";
SyncAdapter orderHeaderSyncAdapter = orderHeaderBuilder.ToSyncAdapter();
orderHeaderSyncAdapter.TableName = "OrderHeader";
this.SyncAdapters.Add(orderHeaderSyncAdapter);
//OrderDetail table.
SqlSyncAdapterBuilder orderDetailBuilder = new SqlSyncAdapterBuilder(serverConn);
orderDetailBuilder.TableName = "Sales.OrderDetail";
orderDetailBuilder.TombstoneTableName = orderDetailBuilder.TableName + "_Tombstone";
orderDetailBuilder.SyncDirection = SyncDirection.Bidirectional;
orderDetailBuilder.CreationTrackingColumn = "InsertTimestamp";
orderDetailBuilder.UpdateTrackingColumn = "UpdateTimestamp";
orderDetailBuilder.DeletionTrackingColumn = "DeleteTimestamp";
orderDetailBuilder.CreationOriginatorIdColumn = "InsertId";
orderDetailBuilder.UpdateOriginatorIdColumn = "UpdateId";
orderDetailBuilder.DeletionOriginatorIdColumn = "DeleteId";
SyncAdapter orderDetailSyncAdapter = orderDetailBuilder.ToSyncAdapter();
orderDetailSyncAdapter.TableName = "OrderDetail";
this.SyncAdapters.Add(orderDetailSyncAdapter);
//Log information for the following events.
this.ChangesSelected += new EventHandler<ChangesSelectedEventArgs>(EventLogger.LogEvents);
this.ChangesApplied += new EventHandler<ChangesAppliedEventArgs>(EventLogger.LogEvents);
this.ApplyChangeFailed += new EventHandler<ApplyChangeFailedEventArgs>(EventLogger.LogEvents);
//Handle the ApplyingChanges event so that we can
//make changes to the dataset.
this.ApplyingChanges += new EventHandler<ApplyingChangesEventArgs>(SampleServerSyncProvider_ApplyingChanges);
}
//Look for inserted rows in the dataset that have a CustomerType
//of Wholesale and update these rows. With access to the dataset,
//you can write any business logic that your application requires.
private void SampleServerSyncProvider_ApplyingChanges(object sender, ApplyingChangesEventArgs e)
{
DataTable customerDataTable = e.Changes.Tables["Customer"];
for (int i = 0; i < customerDataTable.Rows.Count; i++)
{
if (customerDataTable.Rows[i].RowState == DataRowState.Added
&& customerDataTable.Rows[i]["CustomerType"] == "Wholesale")
{
customerDataTable.Rows[i]["CustomerType"] = "Wholesale (from mobile sales)";
}
}
}
}
//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;
//Log information for the following events.
this.SchemaCreated += new EventHandler<SchemaCreatedEventArgs>(EventLogger.LogEvents);
this.ChangesSelected += new EventHandler<ChangesSelectedEventArgs>(EventLogger.LogEvents);
this.ChangesApplied += new EventHandler<ChangesAppliedEventArgs>(EventLogger.LogEvents);
this.ApplyChangeFailed += new EventHandler<ApplyChangeFailedEventArgs>(EventLogger.LogEvents);
//Use the following events to fix up schema on the client.
//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.
//Note that both schema events fire for the Customer table,
//even though we already created the table. This allows us
//to work with the table at this point if we have to.
this.CreatingSchema += new EventHandler<CreatingSchemaEventArgs>(SampleClientSyncProvider_CreatingSchema);
this.SchemaCreated += new EventHandler<SchemaCreatedEventArgs>(SampleClientSyncProvider_SchemaCreated);
}
private void SampleClientSyncProvider_CreatingSchema(object sender, CreatingSchemaEventArgs e)
{
string tableName = e.Table.TableName;
if (tableName == "Customer")
{
//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.
e.Schema.Tables["Customer"].Columns["CustomerId"].RowGuid = true;
}
if (tableName == "OrderHeader")
{
e.Schema.Tables["OrderHeader"].Columns["OrderId"].RowGuid = true;
}
}
private void SampleClientSyncProvider_SchemaCreated(object sender, SchemaCreatedEventArgs e)
{
string tableName = e.Table.TableName;
Utility util = new Utility();
//Call ALTER TABLE on the client. This must be done
//over the same connection and within the same
//transaction that Sync Framework uses
//to create the schema on the client.
if (tableName == "Customer")
{
Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "Customer");
}
if (tableName == "OrderHeader")
{
Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "OrderHeader");
}
if (tableName == "OrderDetail")
{
Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "OrderDetail");
}
}
}
//Handle SyncAgent events and the statistics that are returned by the SyncAgent.
public class SampleStatsAndProgress
{
public void DisplayStats(SyncStatistics syncStatistics, string syncType)
{
Console.WriteLine(String.Empty);
if (syncType == "initial")
{
Console.WriteLine("****** Initial Synchronization Stats ******");
}
else if (syncType == "subsequent")
{
Console.WriteLine("***** Subsequent Synchronization Stats ****");
}
Console.WriteLine("Start Time: " + syncStatistics.SyncStartTime);
Console.WriteLine("Upload Changes Applied: " + syncStatistics.UploadChangesApplied);
Console.WriteLine("Upload Changes Failed: " + syncStatistics.UploadChangesFailed);
Console.WriteLine("Total Changes Uploaded: " + syncStatistics.TotalChangesUploaded);
Console.WriteLine("Download Changes Applied: " + syncStatistics.DownloadChangesApplied);
Console.WriteLine("Download Changes Failed: " + syncStatistics.DownloadChangesFailed);
Console.WriteLine("Total Changes Downloaded: " + syncStatistics.TotalChangesDownloaded);
Console.WriteLine("Complete Time: " + syncStatistics.SyncCompleteTime);
Console.WriteLine(String.Empty);
}
public void DisplaySessionProgress(object sender, EventArgs e)
{
StringBuilder outputText = new StringBuilder();
if (e is SessionStateChangedEventArgs)
{
SessionStateChangedEventArgs args = (SessionStateChangedEventArgs)e;
if (args.SessionState == SyncSessionState.Synchronizing)
{
outputText.AppendLine(String.Empty);
outputText.Append("** SyncAgent is synchronizing");
}
else
{
outputText.Append("** SyncAgent is ready to synchronize");
}
}
else if (e is SessionProgressEventArgs)
{
SessionProgressEventArgs args = (SessionProgressEventArgs)e;
outputText.Append("Percent complete: " + args.PercentCompleted + " (" + args.SyncStage + ")");
}
else
{
outputText.AppendLine("Unknown event occurred");
}
Console.WriteLine(outputText.ToString());
}
}
public class EventLogger
{
//Create client and server log files, and write to them
//based on data from several EventArgs classes.
public static void LogEvents(object sender, EventArgs e)
{
string logFile = String.Empty;
string site = String.Empty;
if (sender is SampleServerSyncProvider)
{
logFile = "ServerLogFile.txt";
site = "server";
}
else if (sender is SampleClientSyncProvider)
{
logFile = "ClientLogFile.txt";
site = "client";
}
StreamWriter streamWriter = File.AppendText(logFile);
StringBuilder outputText = new StringBuilder();
if (e is ChangesSelectedEventArgs)
{
ChangesSelectedEventArgs args = (ChangesSelectedEventArgs)e;
outputText.AppendLine("Client ID: " + args.Session.ClientId);
outputText.AppendLine("Changes selected from " + site + " for group " + args.GroupMetadata.GroupName);
outputText.AppendLine("Inserts selected from " + site + " for group: " + args.Context.GroupProgress.TotalInserts.ToString());
outputText.AppendLine("Updates selected from " + site + " for group: " + args.Context.GroupProgress.TotalUpdates.ToString());
outputText.AppendLine("Deletes selected from " + site + " for group: " + args.Context.GroupProgress.TotalDeletes.ToString());
}
else if (e is ChangesAppliedEventArgs)
{
ChangesAppliedEventArgs args = (ChangesAppliedEventArgs)e;
outputText.AppendLine("Client ID: " + args.Session.ClientId);
outputText.AppendLine("Changes applied to " + site + " for group " + args.GroupMetadata.GroupName);
outputText.AppendLine("Inserts applied to " + site + " for group: " + args.Context.GroupProgress.TotalInserts.ToString());
outputText.AppendLine("Updates applied to " + site + " for group: " + args.Context.GroupProgress.TotalUpdates.ToString());
outputText.AppendLine("Deletes applied to " + site + " for group: " + args.Context.GroupProgress.TotalDeletes.ToString());
}
else if (e is SchemaCreatedEventArgs)
{
SchemaCreatedEventArgs args = (SchemaCreatedEventArgs)e;
outputText.AppendLine("Schema creation for group: " + args.Table.SyncGroup.GroupName);
outputText.AppendLine("Table: " + args.Table.TableName);
outputText.AppendLine("Direction : " + args.Table.SyncDirection);
outputText.AppendLine("Creation Option: " + args.Table.CreationOption);
}
else if (e is ApplyChangeFailedEventArgs)
{
ApplyChangeFailedEventArgs args = (ApplyChangeFailedEventArgs)e;
outputText.AppendLine("** APPLY CHANGE FAILURE AT " + site.ToUpper() + " **");
outputText.AppendLine("Table for which failure occurred: " + args.TableMetadata.TableName);
outputText.AppendLine("Error message: " + args.Error.Message);
}
else
{
outputText.AppendLine("Unknown event occurred");
}
streamWriter.WriteLine(DateTime.Now.ToShortTimeString() + " | " + outputText.ToString());
streamWriter.Flush();
streamWriter.Dispose();
}
}
}
Imports System
Imports System.Collections
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 SampleStatsAndProgress class handles information from the
'SyncStatistics object that the Synchronize method returns and
'from SyncAgent events.
Dim sampleStats As New SampleStatsAndProgress()
'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)
'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 a change at the client that fails when it is
'applied at the server.
Utility.MakeFailingChangeOnClient()
'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()
'Create two SyncGroups so that changes to OrderHeader
'and OrderDetail are made in one transaction. Depending on
'application requirements, you might include Customer
'in the same group.
Dim customerSyncGroup As New SyncGroup("Customer")
Dim orderSyncGroup As New SyncGroup("Order")
'Add each table: specify a synchronization direction of
'Bidirectional.
Dim customerSyncTable As New SyncTable("Customer")
customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
customerSyncTable.SyncDirection = SyncDirection.Bidirectional
customerSyncTable.SyncGroup = customerSyncGroup
Me.Configuration.SyncTables.Add(customerSyncTable)
Dim orderHeaderSyncTable As New SyncTable("OrderHeader")
orderHeaderSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
orderHeaderSyncTable.SyncDirection = SyncDirection.Bidirectional
orderHeaderSyncTable.SyncGroup = orderSyncGroup
Me.Configuration.SyncTables.Add(orderHeaderSyncTable)
Dim orderDetailSyncTable As New SyncTable("OrderDetail")
orderDetailSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable
orderDetailSyncTable.SyncDirection = SyncDirection.Bidirectional
orderDetailSyncTable.SyncGroup = orderSyncGroup
Me.Configuration.SyncTables.Add(orderDetailSyncTable)
'Handle the StateChanged and SessionProgress events, and
'display information to the console.
Dim sampleStats As New SampleStatsAndProgress()
AddHandler Me.StateChanged, AddressOf sampleStats.DisplaySessionProgress
AddHandler Me.SessionProgress, AddressOf sampleStats.DisplaySessionProgress
End Sub 'New
End Class 'SampleSyncAgent
'Create a class that is derived from
'Microsoft.Synchronization.Server.DbServerSyncProvider.
Public Class SampleServerSyncProvider
Inherits DbServerSyncProvider
Public Sub New()
'Create a connection to the sample server database.
Dim util As New Utility()
Dim serverConn As New SqlConnection(Utility.ConnStr_DbServerSync)
Me.Connection = serverConn
'Create a command to retrieve a new anchor value from
'the server. In this case, we use a timestamp value
'that is retrieved and stored in the client database.
'During each synchronization, the new anchor value and
'the last anchor value from the previous synchronization
'are used: the set of changes between these upper and
'lower bounds is synchronized.
'
'SyncSession.SyncNewReceivedAnchor is a string constant;
'you could also use @sync_new_received_anchor directly in
'your queries.
Dim selectNewAnchorCommand As New SqlCommand()
Dim newAnchorVariable As String = "@" + SyncSession.SyncNewReceivedAnchor
With selectNewAnchorCommand
.CommandText = "SELECT " + newAnchorVariable + " = min_active_rowversion() - 1"
.Parameters.Add(newAnchorVariable, SqlDbType.Timestamp)
.Parameters(newAnchorVariable).Direction = ParameterDirection.Output
.Connection = serverConn
End With
Me.SelectNewAnchorCommand = selectNewAnchorCommand
'Create SyncAdapters for each table by using the SqlSyncAdapterBuilder:
' * Specify the base table and tombstone table names.
' * Specify the columns that are used to track when
' and where changes are made.
' * Specify bidirectional synchronization.
' * Call ToSyncAdapter to create the SyncAdapter.
' * Specify a name for the SyncAdapter that matches the
' the name specified for the corresponding SyncTable.
' Do not include the schema names (Sales in this case).
'Customer table
Dim customerBuilder As New SqlSyncAdapterBuilder(serverConn)
With customerBuilder
.TableName = "Sales.Customer"
.TombstoneTableName = customerBuilder.TableName + "_Tombstone"
.SyncDirection = SyncDirection.Bidirectional
.CreationTrackingColumn = "InsertTimestamp"
.UpdateTrackingColumn = "UpdateTimestamp"
.DeletionTrackingColumn = "DeleteTimestamp"
.CreationOriginatorIdColumn = "InsertId"
.UpdateOriginatorIdColumn = "UpdateId"
.DeletionOriginatorIdColumn = "DeleteId"
End With
Dim customerSyncAdapter As SyncAdapter = customerBuilder.ToSyncAdapter()
customerSyncAdapter.TableName = "Customer"
Me.SyncAdapters.Add(customerSyncAdapter)
'OrderHeader table.
Dim orderHeaderBuilder As New SqlSyncAdapterBuilder(serverConn)
With orderHeaderBuilder
.TableName = "Sales.OrderHeader"
.TombstoneTableName = orderHeaderBuilder.TableName + "_Tombstone"
.SyncDirection = SyncDirection.Bidirectional
.CreationTrackingColumn = "InsertTimestamp"
.UpdateTrackingColumn = "UpdateTimestamp"
.DeletionTrackingColumn = "DeleteTimestamp"
.CreationOriginatorIdColumn = "InsertId"
.UpdateOriginatorIdColumn = "UpdateId"
.DeletionOriginatorIdColumn = "DeleteId"
End With
Dim orderHeaderSyncAdapter As SyncAdapter = orderHeaderBuilder.ToSyncAdapter()
orderHeaderSyncAdapter.TableName = "OrderHeader"
Me.SyncAdapters.Add(orderHeaderSyncAdapter)
'OrderDetail table.
Dim orderDetailBuilder As New SqlSyncAdapterBuilder(serverConn)
With orderDetailBuilder
.TableName = "Sales.OrderDetail"
.TombstoneTableName = orderDetailBuilder.TableName + "_Tombstone"
.SyncDirection = SyncDirection.Bidirectional
.CreationTrackingColumn = "InsertTimestamp"
.UpdateTrackingColumn = "UpdateTimestamp"
.DeletionTrackingColumn = "DeleteTimestamp"
.CreationOriginatorIdColumn = "InsertId"
.UpdateOriginatorIdColumn = "UpdateId"
.DeletionOriginatorIdColumn = "DeleteId"
End With
Dim orderDetailSyncAdapter As SyncAdapter = orderDetailBuilder.ToSyncAdapter()
orderDetailSyncAdapter.TableName = "OrderDetail"
Me.SyncAdapters.Add(orderDetailSyncAdapter)
'Log information for the following events.
AddHandler Me.ChangesSelected, AddressOf EventLogger.LogEvents
AddHandler Me.ChangesApplied, AddressOf EventLogger.LogEvents
AddHandler Me.ApplyChangeFailed, AddressOf EventLogger.LogEvents
'Handle the ApplyingChanges event so that we can
'make changes to the dataset.
AddHandler Me.ApplyingChanges, AddressOf SampleServerSyncProvider_ApplyingChanges
End Sub 'New
'Look for inserted rows in the dataset that have a CustomerType
'of Wholesale and update these rows. With access to the dataset,
'you can write any business logic that your application requires.
Private Sub SampleServerSyncProvider_ApplyingChanges(ByVal sender As Object, ByVal e As ApplyingChangesEventArgs)
Dim customerDataTable As DataTable = e.Changes.Tables("Customer")
Dim i As Integer
For i = 1 To customerDataTable.Rows.Count - 1
If customerDataTable.Rows(i).RowState = DataRowState.Added _
AndAlso customerDataTable.Rows(i)("CustomerType") = "Wholesale" Then
customerDataTable.Rows(i)("CustomerType") = "Wholesale (from mobile sales)"
End If
Next i
End Sub
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
'Log information for the following events.
AddHandler Me.SchemaCreated, AddressOf EventLogger.LogEvents
AddHandler Me.ChangesSelected, AddressOf EventLogger.LogEvents
AddHandler Me.ChangesApplied, AddressOf EventLogger.LogEvents
AddHandler Me.ApplyChangeFailed, AddressOf EventLogger.LogEvents
'Use the following events to fix up schema on the client.
'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.
'Note that both schema events fire for the Customer table,
'even though we already created the table. This allows us
'to work with the table at this point if we have to.
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)
Dim tableName As String = e.Table.TableName
If tableName = "Customer" Then
'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.
e.Schema.Tables("Customer").Columns("CustomerId").RowGuid = True
End If
If tableName = "OrderHeader" Then
e.Schema.Tables("OrderHeader").Columns("OrderId").RowGuid = True
End If
End Sub 'SampleClientSyncProvider_CreatingSchema
Private Sub SampleClientSyncProvider_SchemaCreated(ByVal sender As Object, ByVal e As SchemaCreatedEventArgs)
Dim tableName As String = e.Table.TableName
Dim util As New Utility()
'Call ALTER TABLE on the client. This must be done
'over the same connection and within the same
'transaction that Sync Framework uses
'to create the schema on the client.
If tableName = "Customer" Then
Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "Customer")
End If
If tableName = "OrderHeader" Then
Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "OrderHeader")
End If
If tableName = "OrderDetail" Then
Utility.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "OrderDetail")
End If
End Sub 'SampleClientSyncProvider_SchemaCreated
End Class 'SampleClientSyncProvider
'Handle SyncAgent events and the statistics that are returned by the SyncAgent.
Public Class SampleStatsAndProgress
Public Sub DisplayStats(ByVal syncStatistics As SyncStatistics, ByVal syncType As String)
Console.WriteLine(String.Empty)
If syncType = "initial" Then
Console.WriteLine("****** Initial Synchronization Stats ******")
ElseIf syncType = "subsequent" Then
Console.WriteLine("***** Subsequent Synchronization Stats ****")
End If
Console.WriteLine("Start Time: " & syncStatistics.SyncStartTime)
Console.WriteLine("Upload Changes Applied: " & syncStatistics.UploadChangesApplied)
Console.WriteLine("Upload Changes Failed: " & syncStatistics.UploadChangesFailed)
Console.WriteLine("Total Changes Uploaded: " & syncStatistics.TotalChangesUploaded)
Console.WriteLine("Download Changes Applied: " & syncStatistics.DownloadChangesApplied)
Console.WriteLine("Download Changes Failed: " & syncStatistics.DownloadChangesFailed)
Console.WriteLine("Total Changes Downloaded: " & syncStatistics.TotalChangesDownloaded)
Console.WriteLine("Complete Time: " & syncStatistics.SyncCompleteTime)
Console.WriteLine(String.Empty)
End Sub 'DisplayStats
Public Sub DisplaySessionProgress(ByVal sender As Object, ByVal e As EventArgs)
Dim outputText As New StringBuilder()
If TypeOf e Is SessionStateChangedEventArgs Then
Dim args As SessionStateChangedEventArgs = CType(e, SessionStateChangedEventArgs)
If args.SessionState = SyncSessionState.Synchronizing Then
outputText.AppendLine(String.Empty)
outputText.Append("** SyncAgent is synchronizing")
Else
outputText.Append("** SyncAgent is ready to synchronize")
End If
ElseIf TypeOf e Is SessionProgressEventArgs Then
Dim args As SessionProgressEventArgs = CType(e, SessionProgressEventArgs)
outputText.Append("Percent complete: " & args.PercentCompleted & " (" & args.SyncStage.ToString() & ")")
Else
outputText.AppendLine("Unknown event occurred")
End If
Console.WriteLine(outputText.ToString())
End Sub 'DisplaySessionProgress
End Class 'SampleStatsAndProgress
Public Class EventLogger
'Create client and server log files, and write to them
'based on data from several EventArgs classes.
Public Shared Sub LogEvents(ByVal sender As Object, ByVal e As EventArgs)
Dim logFile As String = String.Empty
Dim site As String = String.Empty
If TypeOf sender Is SampleServerSyncProvider Then
logFile = "ServerLogFile.txt"
site = "server"
ElseIf TypeOf sender Is SampleClientSyncProvider Then
logFile = "ClientLogFile.txt"
site = "client"
End If
Dim streamWriter As StreamWriter = File.AppendText(logFile)
Dim outputText As New StringBuilder()
If TypeOf e Is ChangesSelectedEventArgs Then
Dim args As ChangesSelectedEventArgs = CType(e, ChangesSelectedEventArgs)
outputText.AppendLine("Client ID: " & args.Session.ClientId.ToString())
outputText.AppendLine("Changes selected from " & site & " for group " & args.GroupMetadata.GroupName)
outputText.AppendLine("Inserts selected from " & site & " for group: " & args.Context.GroupProgress.TotalInserts.ToString())
outputText.AppendLine("Updates selected from " & site & " for group: " & args.Context.GroupProgress.TotalUpdates.ToString())
outputText.AppendLine("Deletes selected from " & site & " for group: " & args.Context.GroupProgress.TotalDeletes.ToString())
ElseIf TypeOf e Is ChangesAppliedEventArgs Then
Dim args As ChangesAppliedEventArgs = CType(e, ChangesAppliedEventArgs)
outputText.AppendLine("Client ID: " & args.Session.ClientId.ToString())
outputText.AppendLine("Changes applied to " & site & " for group " & args.GroupMetadata.GroupName)
outputText.AppendLine("Inserts applied to " & site & " for group: " & args.Context.GroupProgress.TotalInserts.ToString())
outputText.AppendLine("Updates applied to " & site & " for group: " & args.Context.GroupProgress.TotalUpdates.ToString())
outputText.AppendLine("Deletes applied to " & site & " for group: " & args.Context.GroupProgress.TotalDeletes.ToString())
ElseIf TypeOf e Is SchemaCreatedEventArgs Then
Dim args As SchemaCreatedEventArgs = CType(e, SchemaCreatedEventArgs)
outputText.AppendLine("Schema creation for group: " & args.Table.SyncGroup.GroupName)
outputText.AppendLine("Table: " & args.Table.TableName)
outputText.AppendLine("Direction : " & args.Table.SyncDirection)
outputText.AppendLine("Creation Option: " & args.Table.CreationOption)
ElseIf TypeOf e Is ApplyChangeFailedEventArgs Then
Dim args As ApplyChangeFailedEventArgs = CType(e, ApplyChangeFailedEventArgs)
outputText.AppendLine("** APPLY CHANGE FAILURE AT " & site.ToUpper() & " **")
outputText.AppendLine("Table for which failure occurred: " & args.TableMetadata.TableName)
outputText.AppendLine("Error message: " & args.Error.Message)
Else
outputText.AppendLine("Unknown event occurred")
End If
streamWriter.WriteLine(DateTime.Now.ToShortTimeString() + " | " + outputText.ToString())
streamWriter.Flush()
streamWriter.Dispose()
End Sub 'LogEvents
End Class 'EventLogger