次の方法で共有


イベントを操作する方法とビジネス ロジックをプログラムする方法

このトピックでは、Sync Framework でイベントとビジネス ロジックを操作する方法について説明します。このトピックの例では、次に示す Sync Framework の型とイベントを中心に説明します。

サンプル コードを実行する方法については、「一般的なクライアントとサーバーの同期タスクのプログラミング」の「操作方法に関するトピックのサンプル アプリケーション」を参照してください。

データベース プロバイダーのイベントについて

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 クラスに関するトピック」で説明されている 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

参照

概念

一般的なクライアントとサーバーの同期タスクのプログラミング