Azure SQL データベースのデータを Azure Storage Services にバックアップするサンプル

Azure SQL データベースのデータを DAC Framework を使ってエクスポートし、Azure Storage Services にアップロードするサンプルを書いてみた。バックアップだけでなく、バックアップ一覧の取得と、復旧も実装している。

エミューレータで動くことは確認したけど、本番環境で動くかどうかは確認してない。試用期間終わってまだ契約していないので、そもそも本番環境使えないんだけど。

バックアップ・復旧を行うためのクラスをメモしておく。実装したはいいけど、肝心のデータベースで DAC Framework がサポートしていない型(sql_variant)を使っているため、お蔵入りしそうだし。WorkerRole とかは省略。

using System;
using System.Configuration;
using System.IO;
using System.Runtime.Serialization;
using System.Linq;
using Microsoft.SqlServer.Dac;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace CloudBackupSample
{
    /// <summary>
    /// DAC Framework でエクスポートしたバックアップパッケージを表します。
    /// </summary>
    [DataContract]
    public class BackupPackage
    {
        /// <summary>
        /// <see cref="BackupPackages"/>
        /// クラスの新しいインスタンスを初期化します。
        /// </summary>
        public BackupPackage()
        {
        }

        /// <summary>
        /// データベース名を取得または設定します。
        /// </summary>
        [DataMember]
        public string DatabaseName { get; set; }

        /// <summary>
        /// URL を取得または設定します。
        /// </summary>
        [DataMember]
        public Uri Uri { get; set; }
    }

    /// <summary>
    /// データベースのバックアップパッケージを作成しダウンロードする機能を適用します。
    /// </summary>
    public class CloudBackup
    {
        /// <summary>
        /// Default 接続文字列を取得します。
        /// </summary>
        private string ConnectionString
        {
            get
            {
                // 接続文字列名は適当なものに変更する
                // 外部から設定できるようにしてもいい。
                return ConfigurationManager.ConnectionStrings["Default"].ConnectionString;
            }
        }

        /// <summary>
        /// 指定したデータベースをバックアップパッケージにエクスポートします。
        /// </summary>
        /// <param name="databaseName">バックアップするデータベースの名前</param>
        /// <returns>作成したバックアップパッケージのパス</returns>
        private string ExportBackupPackage(string databaseName)
        {
            // 一時ファイルにエクスポート
            var packageFileName = Path.GetTempFileName();
            var dac = new DacServices(ConnectionString);
            dac.ExportBacpac(packageFileName, databaseName);
            return packageFileName;

        }

        /// <summary>
        /// Windows Azure Storage Servics のアカウントを取得します。
        /// </summary>
        private CloudStorageAccount CreateStorageAccount()
        {
            // 構成名は適当なものに変更する
            return CloudStorageAccount.FromConfigurationSetting("AzureStorageConnectionString");
        }

        private string CreateContainerAddress()
        {
            // とりあえず backup 固定。
            // 本番コードに組み込む場合はちゃんとしたアドレスを生成すること。
            return "backup";
        }

        private string CreateBlobAddressFromDatabaseName(string databaseName)
        {
            // データベース名にタイムスタンプを足す
            return databaseName + DateTime.UtcNow.Ticks;
        }

        /// <summary>
        /// メタデータのキーを定義します。
        /// </summary>
        private class MetadataKey
        {
            public const string DATABASE_NAME = "DatabaseName";
        }

        /// <summary>
        /// 指定したデータベースのバックアップパッケージを
        /// Azure Storage Service にエクスポートします。
        /// </summary>
        /// <param name="databaseName">バックアップするデータベースの名前</param>
        public void ExportToCloudStorage(string databaseName)
        {
            // 一時ファイルにデータベースをエクスポート
            var packageFileName = ExportBackupPackage(databaseName);

            var account = CreateStorageAccount();
            var blobClient = account.CreateCloudBlobClient();

            // バックアップ用のコンテナを取得する
            var containerAddress = CreateContainerAddress();
            var container = blobClient.GetContainerReference(containerAddress);
            container.CreateIfNotExist(); // コンテナが無ければ作成する

            // ブロブを取得する
            var blobAddress = CreateBlobAddressFromDatabaseName(databaseName);
            var blob = container.GetBlobReference(blobAddress);

            // パッケージをアップロード
            blob.UploadFile(packageFileName);

            // メタデータにデータベース名を設定する
            blob.Metadata.Add(MetadataKey.DATABASE_NAME, Uri.EscapeDataString(databaseName));
            blob.SetMetadata();

            // アップロードが完了したらパッケージを削除
            File.Delete(packageFileName);
        }

        /// <summary>
        /// 指定したブロブ上のバックアップパッケージを使ってデータベースを復旧します。
        /// </summary>
        /// <param name="databaseName">復旧先のデータベース名</param>
        /// <param name="blobAddress">バックアップパッケージの URL</param>
        public void ImportFromCloudStorage(string databaseName, string blobAddress)
        {
            var account = CreateStorageAccount();
            var blobClient = account.CreateCloudBlobClient();
            var blob = blobClient.GetBlobReference(blobAddress);

            // バックアップパッケージを Azure Storage Services から
            // 一時フォルダにダウンロード
            var packageFileName = Path.GetTempFileName();
            blob.DownloadToFile(packageFileName);

            // DAC Framework を使ってインポート
            var package = BacPackage.Load(packageFileName);
            var dac = new DacServices(ConnectionString);
            dac.ImportBacpac(package, databaseName);

            // インポートが完了したらパッケージを削除
            File.Delete(packageFileName);
        }

        /// <summary>
        /// Azure Storage Services に保存しているバックアップパッケージ一覧を取得します。
        /// </summary>
        /// <returns>バックアップパッケージ一覧</returns>
        public BackupPackage[] FetchBackupPackagesFromCloudStorage()
        {
            var account = CreateStorageAccount();
            var blobClient = account.CreateCloudBlobClient();
            
            // コンテナを取得
            var containerAddress = CreateContainerAddress();
            var container = blobClient.GetContainerReference(containerAddress);
            container.CreateIfNotExist();

            // ブロブを取得
            return container.ListBlobs()
                .OfType<CloudBlob>()
                .Select(b =>
                {
                    b.FetchAttributes();

                    var dbName = Uri.UnescapeDataString(b.Metadata[MetadataKey.DATABASE_NAME]);
                    return new BackupPackage()
                    {
                        DatabaseName = dbName,
                        Uri = b.Uri,
                    };
                })
                .ToArray();
        }

        /// <summary>
        /// Azure Storage Services に保存しているバックアップパッケージを削除します。
        /// </summary>
        /// <param name="blobAddress">削除するバックアップパッケージの URL</param>
        public void DeleteBackupPackage(string blobAddress)
        {
            var account = CreateStorageAccount();
            var blobClient = account.CreateCloudBlobClient();
            var blob = blobClient.GetBlobReference(blobAddress);
            blob.DeleteIfExists();
        }
    }
}