Der SQL Azure Sync Service

Der SQL Azure Sync Service erlaubt es, eine lokale Datenbank mit einer SQL Azure Datenbank zu synchronisieren. D.h. Änderungen, die in einer der beiden beteiligten Datenbanken vorgenommen werden, können bei Bedarf in die jeweils andere Datenbank übertragen werden. Damit wird es sehr einfach möglich, Offline-Szenarien zu realisieren, in denen beim Ausfall der Internet-Verbindung mit einer lokalen Datenbank weitergearbeitet werden kann. Änderungen, die in dieser Zeit lokal vorgenommen werden, können dann sehr leicht nach SQL Azure übertragen werden, wenn die Verbindung wieder steht.

Das Microsoft Sync Framework

Synchronisation zwischen SQL Azure und einer lokalen Datenbank wird mit Hilfe des Microsoft Sync Frameworks implementiert. In den folgenden Abschnitten soll eine kleine Konsolenanwendung entwickelt werden, die eine Synchronisationsbeziehung zwischen einer lokalen SQL Express Datenbank und einer SQL Azure Datenbank herstellt. Die Architektur ist in Abbildung 1 zu sehen.

image

Abbildung 1: Architektur einer Sync Framework basierten Lösung

Eine auf dem Sync Framework aufsetzende Lösung enthält in der Regel folgende Komponenten, die in der Architekturabbildung zu sehen sind:

  • Sync Provider
    Dies ist eine Art Datenbank-Treiber, der die Funktionen des Sync Frameworks für die beteiligten Datenbanken in die jeweiligen Datenbank-Aufrufe übersetzt. Hier muss also für die betreffende Datenbank ein passender Sync Provider eingesetzt werden. Für SQL Server und SQL Azure stellt Microsoft passende Provider bereit. Für andere Datenbanksysteme sind von den jeweiligen Herstellern ebenfalls Provider verfügbar.
  • Sync Orchestrator
    Diese Komponente konfiguriert die beteiligten Datenbanken, initiiert die Synchronisation und wertet die Ergebnisse aus. Für die Kommunikation mit den Datenbanken bedient er sich der Sync Provider
     

Starten Sie nun Visual Studio und legen über den Menüpunkt File / New / Project ein neues Projekt an. Wählen sie als Projektvorlage eine Konsolenanwendung (Visual C# / Windows / Console Application). Da die Anwendung das Sync Framework nutzen soll, werden vier DLLs benötigt, die in Tabelle 1 aufgelistet sind.

Datei (C:\Program Files\Microsoft Sync Framework\...)

Namespace

2.1\Runtime\x86\ Microsoft.Synchronization.dll

Microsoft.Synchronization

2.1\Runtime\ADO.NET\V3.1\x86\ Microsoft.Synchronization.Data.dll

Microsoft.Synchronization.Data

2.1\Runtime\ADO.NET\V3.1\x86\ Microsoft.Synchronization.Data.Server.dll

Microsoft.Synchronization.Data.Server

2.1\Runtime\ADO.NET\V3.1\x86\ Microsoft.Synchronization.Data.SqlServer.dll

Microsoft.Synchronization.Data.SqlServer

Tabelle 1: Für das Sync Framework benötigte Dateien und Namespaces

Nach Hinzufügen der DLLs sollte der Solution Explorer die entsprechenden Namespaces wie in Abbildung 2 zu sehen auflisten.

image

Abbildung 2: Referenzen zum Sync Framework

Die Konsolenanwendung soll die folgenden zwei Funktionalitäten bieten:

  • Initialisierung der Synchronisationsbeziehung (Methode Setup())
  • Durchführung einer Synchronisation (Methode Sync())
     

Die beiden hierzu benötigten Methoden sind in Listing 1 zu sehen. Sie werden je nach Aufrufparameter aufgerufen. Fehlt der Aufrufparameter, wird eine entsprechende Information auf der Konsole angezeigt.

    1: using System;
    2: using System.Data.SqlClient;
    3: using Microsoft.Synchronization.Data.SqlServer;
    4: using Microsoft.Synchronization.Data;
    5: using Microsoft.Synchronization;
    6:  
    7: namespace SyncTest
    8: {
    9:     class Program
   10:     {
   11:         public static string sqlazureConnectionString =
   12:             @"Server=<MEIN_SERVER>.database.windows.net;
   13:               Database=<MEINE_DATENBANK>; @
   14:               User ID=<MEIN_SERVER_ADMIN_USER>@<MEIN_SERVER>; @
   15:               Password=<MEIN_SERVER_ADMIN_PASSWORT>; @
   16:               Trusted_Connection=False; @
   17:               Encrypt=True;"; @
   18:         public static string sqllocalConnectionString =
   19:             @"Server=.\SQLEXPRESS;
   20:               Database=<MEINE_LOKALE_DATENBANK>; @
   21:               Trusted_Connection=True"; @
   22:  
   23:         public static readonly string scopeName = "alltablesyncgroup";
   24:  
   25:         static void Main(string[] args)
   26:         {
   27:             // Test if input arguments were supplied:
   28:             if (args.Length == 0)
   29:             {
   30:                 System.Console.WriteLine("Please enter an argument.");
   31:                 System.Console.WriteLine("Usage: SyncTest.exe -setup");
   32:                 System.Console.WriteLine("       SyncTest.exe -sync");
   33:             }
   34:  
   35:             else if (args[0] == "-setup")
   36:                 Setup();
   37:             else if (args[0] == "-sync")
   38:                 Sync();
   39:         }
   40:  
   41:         public static void Setup() { ... }
   42:         public static void Sync() { ... }
   43:         }
   44:     }
   45: }

Listing 1: Code-Gerüst für die Sync Anwendung

Die beiden Connection-Strings definieren den Zugang zu den beiden beteiligten Datenbanken. Ersetzen Sie die Platzhalter durch die für Ihre Datenbanken gültigen Werte.

Konfiguration der beteiligten Datenbanken

Bevor eine Synchronisation der Datenbanken durchgeführt werden kann, müssen diese entsprechend vorbereitet werden. Dabei werden in den Datenbanken Hilfstabellen angelegt, in denen Synchronisationsinformationen gespeichert werden. Das Schema der zu synchronisierenden Tabellen bleibt dabei unverändert. Für die initiale Konfiguration der Datenbanken implementieren Sie die Setup()-Methode nun wie in Listing 2 gezeigt. Die dort durchgeführte Konfiguration setzt voraus, dass Sie die SQL Azure Datenbank wie in meinem letzten Blog-Eintrag zu SQL Azure beschrieben aufgesetzt haben, d.h. eine Kunden-Tabelle existiert.

    1: public static void Setup()
    2: {
    3:     try
    4:     {
    5:         SqlConnection sqlServerConn = new SqlConnection(sqllocalConnectionString);
    6:         SqlConnection sqlAzureConn = new SqlConnection(sqlazureConnectionString);
    7:         DbSyncScopeDescription myScope = new DbSyncScopeDescription(scopeName);
    8:  
    9:         DbSyncTableDescription Customer =
   10:             SqlSyncDescriptionBuilder.GetDescriptionForTable("Kunden", sqlAzureConn);
   11:  
   12:         // Add the tables from above to the scope
   13:         myScope.Tables.Add(Customer);
   14:  
   15:         // Setup SQL Server for sync
   16:         SqlSyncScopeProvisioning sqlServerProv =
   17:             new SqlSyncScopeProvisioning(sqlServerConn, myScope);
   18:         if (!sqlServerProv.ScopeExists(scopeName))
   19:         {
   20:             // Apply the scope provisioning.
   21:             Console.WriteLine("Provisioning SQL Server for sync " + DateTime.Now);
   22:             sqlServerProv.Apply();
   23:             Console.WriteLine("Done Provisioning SQL Server for sync " + DateTime.Now);
   24:         }
   25:         else
   26:             Console.WriteLine("SQL Server Database server already provisioned for sync " +
   27:                               DateTime.Now);
   28:  
   29:         // Setup SQL Azure for sync
   30:         SqlSyncScopeProvisioning sqlAzureProv =
   31:             new SqlSyncScopeProvisioning(sqlAzureConn, myScope);
   32:         if (!sqlAzureProv.ScopeExists(scopeName))
   33:         {
   34:             // Apply the scope provisioning.
   35:             Console.WriteLine("Provisioning SQL Azure for sync " + DateTime.Now);
   36:             sqlAzureProv.Apply();
   37:             Console.WriteLine("Done Provisioning SQL Azure for sync " + DateTime.Now);
   38:         }
   39:         else
   40:             Console.WriteLine("SQL Azure Database server already provisioned for sync " +
   41:                               DateTime.Now);
   42:  
   43:         sqlAzureConn.Close();
   44:         sqlServerConn.Close();
   45:     }
   46:     catch (Exception ex)
   47:     {
   48:         Console.WriteLine(ex);
   49:     }
   50: }

Listing 2: Methode zum Initialisieren der Synchronisationsbeziehung

Die Methode baut zunächst Verbindungen zu den beiden Datenbanken (wie über die Connection-Strings konfiguriert) auf und initialisiert einen sogenannten Sync-Scope. Dieser legt den Bereich, d.h. die Tabellen, fest, der synchronisiert werden soll. Danach wird die Schema-Information der Kunden-Tabelle ermittelt und dem Sync-Scope hinzugefügt. Anschließend wird der Sync-Scope zunächst auf der lokalen Datenbank und dann in SQL Azure provisioniert. Abschließend werden die Verbindungen zu den Datenbanken wieder geschlossen. Bauen Sie die Solution durch Auswahl des Menüpunkts Build / Build Solution. Öffnen sie anschließend ein Konsolenfenster und wechseln dort in das Verzeichnis, in das Visual Studio die exe-Datei geschrieben hat. Abbildung 3 zeigt die Konsolenausgabe bei Ausführung der Anwendung (zuerst Aufruf ohne Parameter, dann mit Parameter -setup).

image

Abbildung 3: Initialisierung des Sync Frameworks

Damit ist die Initialisierung der Datenbanken abgeschlossen. Das Sync Framework hat in beiden Datenbanken Hilfstabellen angelegt, in denen Änderungen bis zur nächsten Synchronisation zwischengespeichert werden. Abbildung 4 zeigt die Tabellenansicht aus dem SQL Server Management Studio vor und nach der Initialisierung.

image

Abbildung 4: Tabellen vor und nach Initialisierung des Sync Frameworks

Vor der Initialisierung ist nur in SQL Azure eine Kunden-Tabelle vorhanden. Nach der Initialisierung wurde zum einen die Tabelle auch in der lokalen Datenbank angelegt (ja, dies erledigt das Sync Framework automatisch), zum anderen wurden in beiden Datenbanken die Hilfstabellen angelegt. Noch wurden allerdings keine Daten übertragen. Die Änderungen beziehen sich noch ausschließlich auf die Schemata.

Synchronisation mit einer lokalen Datenbank

Nun soll die Sync()-Methode implementiert werden, mit deren Hilfe eine Synchronisation der beiden Datenbanken, d.h. ein Datenabgleich, erfolgen kann. Implementieren Sie die Methode wie in Listing 3 gezeigt. Die Methode baut Verbindungen zu den beiden Datenbanken auf und instanziiert einen neuen Sync-Orchestrator. Im Orchestrator werden zwei passende Sync-Provider und die gewünschte Synchronisationsrichtung definiert. Im Beispiel soll die Synchronisation in beide Richtungen erfolgen.

    1: public static void Sync()
    2: {
    3:     try
    4:     {
    5:         SqlConnection sqlServerConn = new SqlConnection(sqllocalConnectionString);
    6:         SqlConnection sqlAzureConn = new SqlConnection(sqlazureConnectionString);
    7:         SyncOrchestrator orch = new SyncOrchestrator
    8:         {
    9:             LocalProvider = new SqlSyncProvider(scopeName, sqlServerConn),
   10:             RemoteProvider = new SqlSyncProvider(scopeName, sqlAzureConn),
   11:             Direction = SyncDirectionOrder.UploadAndDownload
   12:         };
   13:  
   14:         Console.WriteLine("ScopeName={0} ", scopeName);
   15:         Console.WriteLine("Starting Sync " + DateTime.Now);
   16:  
   17:         ShowStatistics(orch.Synchronize());
   18:  
   19:         sqlAzureConn.Close();
   20:         sqlServerConn.Close();
   21:     }
   22:     catch (Exception ex)
   23:     {
   24:         Console.WriteLine(ex);
   25:     }
   26: }
   27:  
   28: public static void ShowStatistics(SyncOperationStatistics syncStats)
   29: {
   30:   Console.WriteLine("General Sync Statistics");
   31:   Console.WriteLine("=======================");
   32:   Console.WriteLine("\tSync Start Time : " + syncStats.SyncStartTime.ToString());
   33:   Console.WriteLine("\tSync End Time   : " + syncStats.SyncEndTime.ToString());
   34:   Console.WriteLine("");
   35:   Console.WriteLine("Upload Statistics   (local SQL -> SQL Azure)");
   36:   Console.WriteLine("============================================");
   37:   Console.WriteLine("\tChanges Applied : " + syncStats.UploadChangesApplied.ToString());
   38:   Console.WriteLine("\tChanges Failed  : " + syncStats.UploadChangesFailed.ToString());
   39:   Console.WriteLine("\tChanges Total   : " + syncStats.UploadChangesTotal.ToString());
   40:   Console.WriteLine("");
   41:   Console.WriteLine("Download Statistics (SQL Azure -> local SQL)");
   42:   Console.WriteLine("============================================");
   43:   Console.WriteLine("\tChanges Applied : " + syncStats.DownloadChangesApplied.ToString());
   44:   Console.WriteLine("\tChanges Failed  : " + syncStats.DownloadChangesFailed.ToString());
   45:   Console.WriteLine("\tChanges Total   : " + syncStats.DownloadChangesTotal.ToString());
   46: }

Listing 3: Methode zum Synchronisieren der Inhalte der Datenbanken

Nach erfolgreicher Synchronisation wird das Ergebnis über die Hilfsmethode ShowStatistics() angezeigt. Bauen Sie die Solution und wechseln Sie anschließend in das Konsolenfenster. Rufen Sie dort die Anwendung mit dem Aufrufparameter -sync auf. Abbildung 5 zeigt das Ergebnis.

image

Abbildung 5: Synchronisation der Inhalte aus SQL Azure

Da in der SQL Azure Datenbank nur ein Eintrag vorhanden war (und zwar der, der in meinem letzten Blog Eintrag zu SQL Azure hinzugefügt wurde), zeigt die Ausgabe an, dass eine Änderung von der SQL Azure Datenbank in die lokale Datenbank übertragen wurde. Prüfen Sie (z.B. im SQL Server Management Studio), ob der Eintrag nun korrekt in der lokalen Datenbank liegt.

Ändern Sie nun den Eintrag in der lokalen Datenbank. Hierzu können Sie das SQL Skript verwenden, das in Listing 4 abgebildet ist, und führen Sie dieses im SQL Server Management Studio aus.

    1: UPDATE [LokalDB].[dbo].[Kunden]
    2:    SET [Vorname] = 'Max'
    3:       ,[Nachname] = 'Mustermann'
    4:  WHERE [KundenID] = 1
    5: GO

Listing 4: Änderung eines Eintrags in der lokalen Datenbank

Starten Sie nun einen neuen Synchronisationslauf, indem Sie wieder in das Konsolenfenster wechseln und dort durch Aufruf der Anwendung mittels SyncTest -sync eine Synchronisation der Datenbanken durchfürhen. Das Ergebnis ist in Abbildung 6 zu sehen.

image

Abbildung 6: Synchronisation der Änderung der lokalen Datenbank

In diesem zweiten Synchronisationslauf wurde nun ein Eintrag (der geänderte Eintrag) von der lokalen Datenbank nach SQL Azure übertragen. Abbildung 7 zeigt, wie sich das Ergebnis im SQL Server Management Studio darstellt. Dort ist der geänderte Eintrag in SQL Azure abgebildet.

image

Abbildung 7: Ergebnis der Synchronisation

Weitere Informationen