使用英语阅读

通过


如何初始化客户端数据库和处理表架构

本主题演示如何初始化 Microsoft SQL Server Compact 3.5 SP1 客户端数据库以及如何处理该数据库中的架构。本主题中的示例着重介绍以下 Sync Services 类型和事件:

有关如何运行示例代码的信息,请参见对常见客户端与服务器同步任务进行编程中的“帮助主题中的示例应用程序”。

了解初始化

初始化客户端数据库的操作通常涉及将用户架构和数据复制到数据库,以及在数据库中启用变更跟踪。(不能为快照同步启用变更跟踪。)有关更多信息,请参见用于客户端与服务器同步的体系结构和类。默认情况下,服务器同步提供程序从服务器数据库中复制用户架构和数据,然后由客户端同步提供程序将它们应用于客户端数据库。即便是仅限上载的情况,也会默认在客户端上创建架构。还可以手动创建客户端数据库并指定在进行同步时应在数据库中保留的表。

默认情况下,以下约束不会复制到客户端中:FOREIGN KEY 约束、UNIQUE 约束和 DEFAULT 约束。如示例中所示,如果应用程序需要这些约束,可以添加它们。此外,一些列类型在客户端和服务器上的处理方法不同。有关更多信息,请参见数据类型映射和注意事项 (Sync Services)

在客户端数据库中初始化架构之后,将下载参与快照同步、仅限下载同步和双向同步的表的初始数据,方法是使用为 SelectIncrementalInsertsCommand 属性指定的查询。还会为客户端数据库分配一个客户端 ID。此 ID 是一个通过 ClientId 属性公开的 GUID,而且可以唯一地向服务器标识客户端的身份。在创建客户端数据库之后,可以打包数据库的一个副本,该副本包含作为部署工作一部分的应用程序。在第一次同步过程中,会在每个客户端上重新生成客户端 ID。

示例

默认情况下,初始化客户端数据库的工作不需要编写任何额外代码。有关示例,请参见如何将数据快照下载到客户端中的代码。但是,该 API 提供了极大的灵活性。下面的示例演示四种初始化客户端数据库以及处理客户端数据库中的表架构的方法:

  • 调用 Utility 类的方法初始化 Customer 表。此方法使用 SQL 在客户端上创建架构。Utility 类可以通过 用于 Sync Services 帮助主题的 Utility 类获得。向表中添加服务器上不存在的 SalesNotes 列。此列可以在客户端上使用,但是不会同步其中的数据。如果为 Customer 表定义了一个 SyncTable,则会为 CreationOption 属性指定一个 UseExistingTableOrFail 值。这可确保该表在第一次同步期间不会被覆盖。

  • 默认情况下会初始化 CustomerContact 表。对于此表和接下来的两个表,会指定 DropExistingOrCreateNewTable 值。

  • 通过将一个数据集传递给 SyncSchema 构造函数来初始化 OrderHeader 表。该数据集是通过调用 Utility 类的方法创建的。

  • 通过将 OrderDetail 表及其列添加到使用 OrderHeader 数据集创建的 SyncSchema 对象来初始化该表。如代码中所示,您对于如何在客户端上创建架构拥有极大的控制能力。Sync Services 允许您指定 ADO.NET 公开的各种架构相关属性。

代码还演示了两个与创建架构相关的 SqlCeClientSyncProvider 事件:

  • CreatingSchema. 在创建架构之前引发此事件。此事件用于使用 API 变更架构。

  • SchemaCreated. 在创建架构后引发此事件。此事件用于使用 SQL 变更架构。

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 Utility class handles all functionality that is not
            //directly related to synchronization, such as holding connection 
            //string information and making changes to the server and client databases.
            Utility util = new Utility();

            //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.
            util.SetClientPassword();
            util.RecreateClientDatabase();

            //Create the Customer table on the client by using SQL. We add
            //a SalesNotes column that will not be synchronized.
            //When we create the Customer SyncTable, we specify that
            //Synchronization Services should use an existing table.          
            util.CreateTableOnClient();

            //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.
            util.MakeDataChangesOnServer("Customer");
            util.MakeDataChangesOnClient("Customer");

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

            //Return the server data back to its original state.
            util.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
            //and CustomerContact in the same group.
            SyncGroup customerSyncGroup = new SyncGroup("Customer");
            SyncGroup orderSyncGroup = new SyncGroup("Order");

            //Add each table: specify a synchronization direction of
            //Bidirectional. We create the Customer table before sync:
            //we set CreationOption to UseExistingTableOrFail so
            //we are sure that the table exists.
            SyncTable customerSyncTable = new SyncTable("Customer");
            customerSyncTable.CreationOption = TableCreationOption.UseExistingTableOrFail;
            customerSyncTable.SyncDirection = SyncDirection.Bidirectional;
            customerSyncTable.SyncGroup = customerSyncGroup;
            this.Configuration.SyncTables.Add(customerSyncTable);

            SyncTable customerContactSyncTable = new SyncTable("CustomerContact");
            customerContactSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;
            customerContactSyncTable.SyncDirection = SyncDirection.Bidirectional;
            customerContactSyncTable.SyncGroup = customerSyncGroup;
            this.Configuration.SyncTables.Add(customerContactSyncTable);

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

    //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(util.ServerConnString);
            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, so that all
            //    commands are generated.
            //  * 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);


            //CustomerContact table.
            SqlSyncAdapterBuilder customerContactBuilder = new SqlSyncAdapterBuilder(serverConn);

            customerContactBuilder.TableName = "Sales.CustomerContact";
            customerContactBuilder.TombstoneTableName = customerContactBuilder.TableName + "_Tombstone";
            customerContactBuilder.SyncDirection = SyncDirection.Bidirectional;
            customerContactBuilder.CreationTrackingColumn = "InsertTimestamp";
            customerContactBuilder.UpdateTrackingColumn = "UpdateTimestamp";
            customerContactBuilder.DeletionTrackingColumn = "DeleteTimestamp";
            customerContactBuilder.CreationOriginatorIdColumn = "InsertId";
            customerContactBuilder.UpdateOriginatorIdColumn = "UpdateId";
            customerContactBuilder.DeletionOriginatorIdColumn = "DeleteId";
            
            SyncAdapter customerContactSyncAdapter = customerContactBuilder.ToSyncAdapter();
            customerContactSyncAdapter.TableName = "CustomerContact";
            this.SyncAdapters.Add(customerContactSyncAdapter);


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

            //Create the schema for the OrderHeader and OrderDetail tables.
            //We first create a schema based on a DataSet that contains only
            //the OrderHeader table. As with the SyncAdapter, the table name
            //must match the SyncTable name. We then add the schema for the 
            //OrderDetail table; this is the place to map data types if
            //your application requires it.
            DataSet orderHeaderDataSet = util.CreateDataSetFromServer();
            orderHeaderDataSet.Tables[0].TableName = "OrderHeader";
            this.Schema = new SyncSchema(orderHeaderDataSet);
            
            this.Schema.Tables.Add("OrderDetail");

            this.Schema.Tables["OrderDetail"].Columns.Add("OrderDetailId");
            this.Schema.Tables["OrderDetail"].Columns["OrderDetailId"].ProviderDataType = "int";
            this.Schema.Tables["OrderDetail"].Columns["OrderDetailId"].AllowNull = false;

            this.Schema.Tables["OrderDetail"].Columns.Add("OrderId");
            this.Schema.Tables["OrderDetail"].Columns["OrderId"].ProviderDataType = "uniqueidentifier";
            this.Schema.Tables["OrderDetail"].Columns["OrderId"].RowGuid = true;
            this.Schema.Tables["OrderDetail"].Columns["OrderId"].AllowNull = false;

            this.Schema.Tables["OrderDetail"].Columns.Add("Product");
            this.Schema.Tables["OrderDetail"].Columns["Product"].ProviderDataType = "nvarchar";
            this.Schema.Tables["OrderDetail"].Columns["Product"].MaxLength = 100;
            this.Schema.Tables["OrderDetail"].Columns["Product"].AllowNull = false;

            this.Schema.Tables["OrderDetail"].Columns.Add("Quantity");
            this.Schema.Tables["OrderDetail"].Columns["Quantity"].ProviderDataType = "int";
            this.Schema.Tables["OrderDetail"].Columns["Quantity"].AllowNull = false;
           
            //The primary key columns are passed as a string array.
            string[] orderDetailPrimaryKey = new string[2];
            orderDetailPrimaryKey[0] = "OrderDetailId";
            orderDetailPrimaryKey[1] = "OrderId";
            this.Schema.Tables["OrderDetail"].PrimaryKey = orderDetailPrimaryKey;
        }
    }

    //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 = util.ClientConnString;

            //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 need 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;
            
            Console.Write("Creating schema for " + tableName + " | ");
            
            if (tableName == "OrderHeader")
            {
                //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["OrderHeader"].Columns["OrderId"].RowGuid = true;
                
            }

            if (tableName == "OrderDetail")
            {
                //Add a foreign key between the OrderDetail and OrderHeader tables.
                e.Schema.Tables["OrderDetail"].ForeignKeys.Add("FK_OrderDetail_OrderHeader", "OrderHeader", "OrderId", "OrderDetail", "OrderId");
            }
        }

        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 Synchronization Services uses
            //to create the schema on the client.
            if (tableName == "OrderHeader")
            {
                util.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "OrderHeader");                
            }

            if (tableName == "OrderDetail")
            {
                util.MakeSchemaChangesOnClient(e.Connection, e.Transaction, "OrderDetail");                
            }

            Console.WriteLine("Schema created for " + 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);
        }
    }
}

请参阅

概念

对常见客户端与服务器同步任务进行编程