How to: Backup and Restore a Database (SQL Server)

This topic describes how to back up and restore a SQL Server database that is synchronized by using Sync Framework. The code in this application focuses on the following Sync Framework classes:

For information about how to run sample code, see "Example Applications in the How to Topics" in Synchronizing SQL Server and SQL Server Compact.

Understanding Backup and Restore for Databases That Are Synchronized

As with any production database, a server database that is involved in synchronization should be backed up regularly. If you restore a database from a backup, Sync Framework must update synchronization metadata to address issues with changes that can occur after the backup was taken. Changes fall into three categories:

  • The change was made at a client or another peer server.

    After the server is restored, synchronizing with other nodes will ultimately bring the server up-to-date with these remote changes.

  • The change was made at the server, and it was propagated to at least one client or peer server.

    After the server is restored, synchronizing with other nodes will ultimately bring the server up-to-date with these local changes that were not included in the backup. Metadata is updated to handle this case properly.

  • The change was made at the server, but it was not propagated to any clients or peer servers.

    As with other situations in which a change is not part of a backup, changes of this type cannot be restored.

Updating Metadata after Restoring a Server

For every change that occurs in a table, Sync Framework updates metadata on the server to identify the time and origin of the change. This metadata is used by other nodes to determine which rows each node requires from the server. If changes occur at the server after a backup and those changes are propagated to other nodes before a restore, the other nodes will contain metadata for each of the changed rows. After the server is restored from the backup, subsequent changes on the server might be assigned identifiers that collide with ones that the other nodes already contain. This can cause conflicts during synchronization and possible non-convergence.

To avoid these issues, Sync Framework assigns a new replica ID to the server and updates several metadata columns for each table that is synchronized.

To Restore the Server and Update Metadata

  1. Establish exclusive access to the server database. This prevents any local operations or synchronization operations from updating data or metadata until the database is ready.

  2. Restore the SQL Server database from a full backup, and any differential backups if appropriate.

  3. Execute PerformPostRestoreFixup.

  4. Enable full access to the database. At this point, local changes can occur on the server; and clients and other peer servers can synchronize with the server.

Examples

The following code example performs a backup of the server database and makes changes in the database by calling methods in the sample Utility class, and then synchronizes those changes.

// Backup the server database.         
Utility.CreateDatabaseBackup();

// Make changes on the server: 1 insert, 1 update, and 1 delete.
Utility.MakeDataChangesOnNode(Utility.ConnStr_SqlSync_Server, "Customer");                            

// Synchronize the three changes.
syncOrchestrator = new SampleSyncOrchestrator(
    new SqlCeSyncProvider("customer", clientSqlCe1Conn),
    new SqlSyncProvider("customer", serverConn)
    );
syncStats = syncOrchestrator.Synchronize();
syncOrchestrator.DisplayStats(syncStats, "subsequent");

The application calls a stored procedure (usp_SampleDbBackupRestore) that contains the following Transact-SQL code to back up the server database.

BACKUP DATABASE [SyncSamplesDb_SqlPeer1] 
TO  DISK = N'C:\Program Files\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\Backup\SyncSamplesDb_SqlPeer1.bak' 
WITH NOFORMAT, NOINIT,  NAME = N'SyncSamplesDb_SqlPeer1-Full Database Backup', SKIP, NOREWIND, NOUNLOAD,  STATS = 10

The same procedure also contains Transact-SQL code to restore the server database.

-- Backup the tail of the log.
BACKUP LOG [SyncSamplesDb_SqlPeer1] 
TO  DISK = N'C:\Program Files\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\Backup\SyncSamplesDb_SqlPeer1.bak' 
WITH  NO_TRUNCATE , NOFORMAT, NOINIT,  NAME = N'TestBackupRestore-Transaction Log  Backup', SKIP, NOREWIND, NOUNLOAD,  NORECOVERY ,  STATS = 10

-- Restore the database.
RESTORE DATABASE [SyncSamplesDb_SqlPeer1] 
FROM  DISK = N'C:\Program Files\Microsoft SQL Server\MSSQL10.MSSQLSERVER\MSSQL\Backup\SyncSamplesDb_SqlPeer1.bak' 
WITH FILE = 1,  NOUNLOAD,  STATS = 10

The following code example restores the server database and calls PerformPostRestoreFixup() to update metadata. The application then synchronizes all nodes again.

Utility.RestoreDatabaseFromBackup();


// Call the API to update synchronization metadata to reflect that the database was
// just restored. The restore stored procedure kills the connection to the
// server, so we must re-establish it.
SqlConnection.ClearPool(serverConn);
serverConn = new SqlConnection(Utility.ConnStr_SqlSync_Server);
SqlSyncStoreRestore databaseRestore = new SqlSyncStoreRestore(serverConn);
databaseRestore.PerformPostRestoreFixup();

Complete Code Example

The following complete code example includes the code examples that are described earlier and additional code. The example requires the Utility class that is available in Utility Class for Database Provider How-to Topics.


// NOTE: Before running this application, run the database sample script that is
// available in the documentation. The script drops and re-creates the tables that 
// are used in the code, and ensures that synchronization objects are dropped so that 
// Sync Framework can re-create them.

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.SqlServer;
using Microsoft.Synchronization.Data.SqlServerCe;

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

            // Create the connections over which provisioning and synchronization
            // are performed. The Utility class handles all functionality that is not
            //directly related to synchronization, such as holding connection 
            //string information and making changes to the server database.
            SqlConnection serverConn = new SqlConnection(Utility.ConnStr_SqlSync_Server);
            SqlConnection clientSqlConn = new SqlConnection(Utility.ConnStr_SqlSync_Client);
            SqlCeConnection clientSqlCe1Conn = new SqlCeConnection(Utility.ConnStr_SqlCeSync1);

            // Create a scope named "customer", and add the Customer table to the scope.
            // GetDescriptionForTable gets the schema of the table, so that tracking 
            // tables and triggers can be created for that table.
            DbSyncScopeDescription scopeDesc = new DbSyncScopeDescription("customer");

            scopeDesc.Tables.Add(
            SqlSyncDescriptionBuilder.GetDescriptionForTable("Sales.Customer", serverConn));

            // Create a provisioning object for "customer" and specify that
            // base tables should not be created (They already exist in SyncSamplesDb_SqlPeer1).
            SqlSyncScopeProvisioning serverConfig = new SqlSyncScopeProvisioning(scopeDesc);
            serverConfig.SetCreateTableDefault(DbSyncCreationOption.Skip);

            // Configure the scope and change-tracking infrastructure.
            serverConfig.Apply(serverConn);

            // Retrieve scope information from the server and use the schema that is retrieved
            // to provision the SQL Server and SQL Server Compact client databases.           

            // This database already exists on the server.
            DbSyncScopeDescription clientSqlDesc = SqlSyncDescriptionBuilder.GetDescriptionForScope("customer", serverConn);
            SqlSyncScopeProvisioning clientSqlConfig = new SqlSyncScopeProvisioning(clientSqlDesc);
            clientSqlConfig.Apply(clientSqlConn);

            // This database does not yet exist.
            Utility.DeleteAndRecreateCompactDatabase(Utility.ConnStr_SqlCeSync1, true);
            DbSyncScopeDescription clientSqlCeDesc = SqlSyncDescriptionBuilder.GetDescriptionForScope("customer", serverConn);
            SqlCeSyncScopeProvisioning clientSqlCeConfig = new SqlCeSyncScopeProvisioning(clientSqlCeDesc);
            clientSqlCeConfig.Apply(clientSqlCe1Conn);


            // Initial synchronization sessions.
            SampleSyncOrchestrator syncOrchestrator;
            SyncOperationStatistics syncStats;

            // Data is downloaded from the server to the SQL Server client.
            syncOrchestrator = new SampleSyncOrchestrator(
                new SqlSyncProvider("customer", clientSqlConn),
                new SqlSyncProvider("customer", serverConn)
                );
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "initial");

            // Data is downloaded from the SQL Server client to the 
            // SQL Server Compact client.
            syncOrchestrator = new SampleSyncOrchestrator(
                new SqlCeSyncProvider("customer", clientSqlCe1Conn),
                new SqlSyncProvider("customer", clientSqlConn)
                );
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "initial");


            // Remove the backup file because this application requires a drop and recreation
            // of the sample database, and the backup must be recreated each time
            // the application runs.
            Utility.DeleteDatabaseBackup();


            // Backup the server database.         
            Utility.CreateDatabaseBackup();

            // Make changes on the server: 1 insert, 1 update, and 1 delete.
            Utility.MakeDataChangesOnNode(Utility.ConnStr_SqlSync_Server, "Customer");                            
            
            // Synchronize the three changes.
            syncOrchestrator = new SampleSyncOrchestrator(
                new SqlCeSyncProvider("customer", clientSqlCe1Conn),
                new SqlSyncProvider("customer", serverConn)
                );
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "subsequent");

            syncOrchestrator = new SampleSyncOrchestrator(
                new SqlSyncProvider("customer", clientSqlConn),
                new SqlCeSyncProvider("customer", clientSqlCe1Conn)
                );
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "subsequent");

            // Restore the server database from backup. The restored version
            // does not contain the three changes that were just synchronized.
            Utility.RestoreDatabaseFromBackup();


            // Call the API to update synchronization metadata to reflect that the database was
            // just restored. The restore stored procedure kills the connection to the
            // server, so we must re-establish it.
            SqlConnection.ClearPool(serverConn);
            serverConn = new SqlConnection(Utility.ConnStr_SqlSync_Server);
            SqlSyncStoreRestore databaseRestore = new SqlSyncStoreRestore(serverConn);
            databaseRestore.PerformPostRestoreFixup();


            // Synchronize a final time.
            syncOrchestrator = new SampleSyncOrchestrator(
                new SqlCeSyncProvider("customer", clientSqlCe1Conn),
                new SqlSyncProvider("customer", serverConn)
                );
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "subsequent");

            syncOrchestrator = new SampleSyncOrchestrator(
                new SqlSyncProvider("customer", clientSqlConn),
                new SqlCeSyncProvider("customer", clientSqlCe1Conn)
                );
            syncStats = syncOrchestrator.Synchronize();
            syncOrchestrator.DisplayStats(syncStats, "subsequent");

            serverConn.Close();
            serverConn.Dispose();
            clientSqlConn.Close();
            clientSqlConn.Dispose();
            clientSqlCe1Conn.Close();
            clientSqlCe1Conn.Dispose();

            Console.Write("\nPress any key to exit.");
            Console.Read();
            
        }

    }

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

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

        public void DisplayStats(SyncOperationStatistics syncStatistics, string syncType)
        {
            Console.WriteLine(String.Empty);
            if (syncType == "initial")
            {
                Console.WriteLine("****** Initial Synchronization ******");
            }
            else if (syncType == "subsequent")
            {
                Console.WriteLine("***** Subsequent Synchronization ****");
            }

            Console.WriteLine("Start Time: " + syncStatistics.SyncStartTime);
            Console.WriteLine("Total Changes Uploaded: " + syncStatistics.UploadChangesTotal);
            Console.WriteLine("Total Changes Downloaded: " + syncStatistics.DownloadChangesTotal);
            Console.WriteLine("Complete Time: " + syncStatistics.SyncEndTime);
            Console.WriteLine(String.Empty);
        }
    }
}

See Also

Show: