しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

SQLite の代わりにシリアライズでローカルにデータ保存する

Windows アプリでローカルにデータを永続化する場合、SQLite を利用している方が多いのですが、なぜ XmlSerializerを利用する方は少ないんでしょうか。

XmlSerializer を利用したローカルデータ保存はこんな感じでやっています

/// <summary>
/// アプリケーション設定情報リポジトリ
/// </summary>
public class ApplicationSettingsRepository
{
    #region Privates

    /// <summary>
    /// データの参照先フォルダ
    /// </summary>
    private static readonly IStorageFolder StoreFolder = ApplicationData.Current.LocalFolder;

    /// <summary>
    /// アプリケーション設定情報のファイル名
    /// </summary>
    private static readonly string FileName = @"app-settings.xml";

    /// <summary>
    /// アプリケーション設定情報のデフォルト設定ファイル名
    /// </summary>
    private static readonly string DefaultFilePath = @"ms-appx:///Assets/Data/default-app-settings.xml";

    #endregion //Privates

    /// <summary>
    /// コンストラクタ
    /// </summary>
    static ApplicationSettingsRepository()
    {
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public ApplicationSettingsRepository()
    {
    }

    /// <summary>
    /// アプリケーション設定情報
    /// </summary>
    public ApplicationSettings Settings { get; private set; }

    /// <summary>
    /// アプリケーション設定情報を読み込む
    /// </summary>
    /// <returns>成功した場合は true, 失敗した場合は false</returns>
    public async Task<bool> LoadAsync()
    {
        try
        {
            var file = await StoreFolder.GetFileAsync(FileName);
            using (var stream = await file.OpenSequentialReadAsync())
            {
                var serializer = new XmlSerializer(typeof(ApplicationSettings));
                this.Settings = serializer.Deserialize(stream.AsStreamForRead()) as ApplicationSettings;
                ApplicationSettings.IsSynchronize = true;
            }

            return true;
        }
        catch (FileNotFoundException)
        {
            Debug.WriteLine(string.Format("{0} がないのでデフォルトの設定情報を読み込みます。", FileName));
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
        }

        // 読み込みに失敗した場合はデフォルト値を読み込む
        try
        {
            var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri(DefaultFilePath, UriKind.Absolute));
            using (var stream = await file.OpenSequentialReadAsync())
            {
                var serializer = new XmlSerializer(typeof(ApplicationSettings));
                this.Settings = serializer.Deserialize(stream.AsStreamForRead()) as ApplicationSettings;
                ApplicationSettings.IsSynchronize = true;
            }

            await this.SaveAsync();

            return true;
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
        }

        return false;
    }

    /// <summary>
    /// アプリケーション設定情報を書き込む
    /// </summary>
    /// <returns>成功した場合は true, 失敗した場合は false</returns>
    public async Task<bool> SaveAsync()
    {
        try
        {
            ApplicationSettings.LockObject.WaitOne();

            var file = await StoreFolder.CreateFileAsync(FileName, CreationCollisionOption.ReplaceExisting);
            using (var stream = await file.OpenStreamForWriteAsync())
            {
                var writer = XmlWriter.Create(
                        stream,
                        new XmlWriterSettings()
                        {
                            Encoding = Encoding.UTF8,
                            Indent = false,
                            NewLineChars = string.Empty,
                        });

                var serializer = new XmlSerializer(typeof(ApplicationSettings));
                serializer.Serialize(writer, this.Settings);
            }

            return true;
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.ToString());
        }
        finally
        {
            ApplicationSettings.LockObject.ReleaseMutex();
        }

        return false;
    }
}

読み込みと書き込みを制御する Repository クラスを作成しまして・・・

/// <summary>
/// アプリケーション設定情報
/// </summary>
[XmlRoot("app-setting")]
public partial class ApplicationSettings : IApplicationSettings
{
    #region マルチスレッド排他制御用

    /// <summary>
    /// 排他制御フラグ
    /// </summary>
    private static bool isSynchronize = false;

    /// <summary>
    /// 排他制御フラグ
    /// </summary>
    [XmlIgnore]
    public static bool IsSynchronize
    {
        get { return isSynchronize; }
        set { isSynchronize = value; }
    }

    /// <summary>
    /// 排他制御オブジェクト
    /// </summary>
    public static readonly Mutex LockObject = new Mutex();

    #endregion //マルチスレッド排他制御用

    #region IsFirstRun:初回起動フラグ プロパティ
    /// <summary>
    /// 初回起動フラグ
    /// </summary>
    private bool isFirstRun; 

    /// <summary>
    /// 初回起動フラグ の取得および設定
    /// </summary>
    [XmlAttribute("first-run")]
    public bool IsFirstRun
    {
        get { return this.isFirstRun; }
        set 
        {
            try
            {
                if (isSynchronize)
                {
                    LockObject.WaitOne();
                }

                this.isFirstRun = value;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (isSynchronize)
                {
                    LockObject.ReleaseMutex();
                }
            }
        }
    }
    #endregion //IsFirstRun:初回起動フラグ プロパティ

    #region Items:写真情報 プロパティ
    /// <summary>
    /// 写真情報
    /// </summary>
    private List<Photo> items; 

    /// <summary>
    /// 写真情報 の取得および設定
    /// </summary>
    [XmlElement("photo")]
    public List<Photo> Items
    {
        get { return this.items; }
        set 
        {
            try
            {
                if (isSynchronize)
                {
                    LockObject.WaitOne();
                }

                this.items = value;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                if (isSynchronize)
                {
                    LockObject.ReleaseMutex();
                }
            }
        }
    }
    #endregion //Items:写真情報 プロパティ

}

こんな感じの排他制御オブジェクトつきの Model をデシリアライズシリアライズすれば済んでしまいます

Model の部分は冗長なので T4 テンプレートで自動生成しています(Repository 側も自動生成できるかも)

Model には Portable 対応の処理しか記述せず、Repository にプラットフォーム固有の処理を記述するのでクロスプラットフォーム対応もしやすいです

LINQ to Object を使えば SQL と似たような問い合わせはできますし、おそらく SQLLite を利用するより直にシリアライズした方が速度的にも速いのではないか?と思うのですが・・・

SQLite がマルチスレッド対応なのかどうかもいまいちはっきりしていないのと、トランザクション制御ができるといっても本家 DBMS ほど堅牢なわけでもなさそうですし・・・SQLite を利用している記事を見てもその採用理由がはっきりしていないことが多いような?!