情報
要求されたトピックは次のとおりです。しかし、このトピックはこのライブラリには含まれていません。

Windows Phone 8 のローカル データベースのベスト プラクティス

2014/06/18

対象: Windows Phone 8 および Windows Phone Silverlight 8.1 | Windows Phone OS 7.1

Windows Phone OS 7.1 以降、アプリのローカル フォルダーに存在するローカル データベースにリレーショナル データを格納できます。このトピックでは、ローカル データベースを操作する場合に考慮すべきさまざまなベスト プラクティスをまとめています。Windows Phone アプリでのローカル データベースの使用に関する手順を追ったガイダンスについては、「Windows Phone 8 の基本的なローカル データベース アプリを作成する方法」および「Windows Phone 8 用の MVVM でローカル データベース アプリを作成する方法」を参照してください。

このトピックは、次のセクションで構成されています。

データ仮想化と Skip/Take の使用

ListBox コントロールでは、Skip メソッドと Take メソッドを使用して、ユーザーがリストをスクロールした場合に、該当する項目をまとめてフェッチすることができます。データバインド ListBox コントロールでデータ仮想化を有効にする詳細については、「ListBoxWindows Phone 7 の ListBox パフォーマンスの向上: データ仮想化」を参照してください。

Skip メソッドと Take メソッドを使用すれば、ListBox コントロールに表示するためにデータが必要になるまで、データベースからメモリにデータが読み込まれません。たとえば、次のコードに、データベースから 501 ~ 550 のレコードを取得する方法を示します。

return
(from f in App.FeedsDB.Feeds                    
select f).Skip(500).Take(50);

ユーザーがリストをスクロールすると同時に、この読み込み処理を実行することは負荷が大きいため、この手法はデータ セットが大きい (150 項目以上) シナリオに制限する必要があります。小さいデータ セットの場合は、コレクション全体をメモリに読み込み、それにバインドする方が、パフォーマンスが向上する可能性があります。

双方向のデータ バインディング

LINQ to SQL データ コンテキストのエンティティには、他の POCO (Plain Old CLR Object) と同様に UI コントロールにバインドされた双方向データがあります。エンティティにデータバインドするときは、以下の点を考慮してください。

  • ローカル データベースとの関係を維持するには、アプリはサブセット プロジェクションでなく、完全なエンティティにバインドする必要があります。

  • データ コンテキストのエンティティへの変更を保持するには、SubmitChanges メソッドを使用します。

大きなバッチ編集

まれに、アプリで、ローカル データベース内の大半またはすべてのレコードを更新する必要がある場合があります。たとえば、10,000 人の顧客のテーブルで、CustomerType というプロパティがあるとします。CustomerType プロパティには、StandardPremiere、または Exclusive の値を割り当ることができます。CustomerType として Exclusive が割り当てられている 5,000 人の顧客がいるとして、このカテゴリの名前を Platinum に変更する必要があります。

推奨していない 1 つの方法は、すべての Exclusive の顧客を選択し、各顧客をループして、種類を Platinum に変更することです。データ コンテキストにこれらのすべての変更が行われた後に、SubmitChanges メソッドを呼び出します。このシナリオでは、5,000 個のすべてのオブジェクトをメモリに読み込むことになり、デバイスに問題が発生する可能性があります。

代わりに、デバイス メモリに負担が少ない方法をお勧めします。個別のデータ コンテキストを使用して一括して更新を実行します。この方法では、リストを並べ替え、Skip メソッドと Take メソッドを使用して、データが適切にページングされるようにするクエリを採用します。バッチが完了したら、DataContext オブジェクトで Dispose を呼び出して、コンテキストおよび関連するキャッシュ済みオブジェクトをガベージ コレクターによってクリーンアップします。別の方法として、using ステートメントで DataContext オブジェクトをカプセル化して、自動的に破棄することもできます。

バージョン列による高速更新の有効化

テーブルへの更新操作のパフォーマンスを最適化する最も簡単な方法の 1 つが、バージョン列を追加することです。この最適化は、Windows Phone の LINQ to SQL に固有です。たとえば、エンティティに次のコードを追加します。

        [Column(IsVersion=true)]
        private Binary _version;

この最適化の実装によって、大きな更新のパフォーマンスが大幅に向上する可能性があります。ローカル データベース アプリでこの最適化を使用する例については、「Windows Phone 8 用の MVVM でローカル データベース アプリを作成する方法」を参照してください。

データベースへの変更の送信

データベースに変更を送信する場合、最適なバッチ サイズと送信の頻度の決定は、アプリ シナリオに大きく依存します。次のことを考慮します。

  • SubmitChanges メソッドは、変更が実際にファイル システムに永続化されるまで戻りません。これはいくらかの時間がかかる可能性があり、安易に実行すべきではありません。たとえば、アプリで、ユーザーが CheckBox コントロールを選択または選択解除するたびに変更を送信する場合は、大きなオーバーヘッドが発生します。

  • ただし、トランザクションを送信するために必要な時間は変更の回数と共に増します。アプリで終了時に著しく大きなバッチを送信しようとすると、アプリのプロセスがシャットダウンする前に、完了しない可能性が十分にあります。

一般に、ユーザー操作によって直接生じた変更は、より積極的に送信したいと考えるものです。そのデータが失われた場合、復元する方法がないためです。復元可能な変更 (たとえば、クラウド サービスからのキャッシュされたデータなど) の場合、大きな変更セットを構築する余裕があります。これは、終了時にデータが正常にコミットされなかった場合、必ずデータを復元できるためです。

アプリで、大量のバイナリ ラージ オブジェクト (BLOB) データを保存し、取得する必要がある場合、そのデータをローカル データベースまたはローカル フォルダーに直接保存することができます。

binary(n)varbinary(n)、または image データ型を使用して、BLOB データを直接ローカル データベースに保存できます。この方法を使用する場合、さまざまな最大バッファー サイズ値を使用して、BLOB クエリ パフォーマンスをテストすることをお勧めします。接続文字列の max buffer size パラメーターを大きくすると、BLOB の取得パフォーマンスが向上する可能性がありますが、アプリのメモリ消費量も増えます。接続文字列の詳細については、「Windows Phone 8 のローカル データベース接続文字列」を参照してください。

または、BLOB データをローカル フォルダーに直接保存することもできます。この方法では、アプリは BLOB データへのパスのみをローカル データベースに保存できます。ローカル データベース ファイルはローカル フォルダーにも存在するため、ローカル フォルダーとローカル データベースに保存された BLOB データは、アプリがインストールされている限り、デバイス上に保持できます。ローカル フォルダーの操作の詳細については、「Windows Phone 8 のデータ」を参照してください。

INotifyPropertyChanging

LINQ to SQL の変更追跡は、各オブジェクトの 2 つのコピーを維持することによって機能します。オブジェクトの 1 つのコピーは、データベースから最初に具体化された状態で維持されます。もう 1 つのコピーは、アプリによって変更されます。変更の送信時に、LINQ to SQL は更新されたプロパティを判断し、それらの変更のみをデータベース トランザクションに送信できます。

既定で、オブジェクトが具体化されるときに、LINQ to SQL はオブジェクトの 2 つのコピーを作成します。ただし、特定のトランザクション内で、具体化されたコレクション内のごく少数のオブジェクトしか実際に変更されないこともよくあります。この場合、オブジェクトの 2 つ目のコピーを維持する理由はありません。

アプリで INotifyPropertyChanging インターフェイスを使用して、最終的にデータベースに更新として送信されるプロパティを変更する時点で、DataContext に通知することができます。DataContext はその通知をコピー作成のトリガーとして使用できます。このように、実際に変更される項目のみを複製する必要があります。

エンティティに次のコードを追加し、さらに各エンティティ プロパティのセッターで値が変更される直前に NotifyPropertyChanging メソッドを呼び出すことによって、INotifyPropertyChanging インターフェイスを実装します。

        public event PropertyChangingEventHandler PropertyChanging;

        // Used to notify that a property is about to change
        private void NotifyPropertyChanging(string propertyName)
        {
            if (PropertyChanging != null)
            {
                PropertyChanging(this, new PropertyChangingEventArgs(propertyName));
            }
        }

INotifyPropertyChanging インターフェイスの使用例については、「Windows Phone 8 用の MVVM でローカル データベース アプリを作成する方法」を参照してください。

ObjectTrackingEnabled

モバイル アプリでは、読み取り専用クエリを使用する場合が多くあります。そのような場合、データは存在している必要があるだけで、ユーザーが変更する機会はありません。この場合、変更追跡インフラストラクチャはコスト節約のために完全にオフにできます。これは、DataContextObjectTrackingEnabled プロパティを false に設定して実行します。

メモメモ:

この設定は、設定先の DataContext オブジェクトのみに適用されます。別の DataContext オブジェクトを使用して、同じ行 (読み取り専用でない行) に対して操作を実行することができます。

高速アプリ切り替え

ユーザーがアプリから移動し、オペレーティング システムがそのアプリを休止状態にすると、すべてのアプリ スレッドが停止し、それ以上処理が行われません。ただし、完全に停止できない一部のデータベース操作など、アプリに代わって、オペレーティング システムによって実行される特定の操作があります。

特に、大量のグループ化や並べ替えなどがある著しく複雑なクエリは、実行に時間がかかる可能性があり、アプリが一時停止を許可されている時間より長く実行される可能性があります。その場合、アプリはオペレーティング システムによって完全に廃棄されます。アプリで、高速アプリ切り替えを十分に活用するには、非アクティブ化中に、負荷の大きいクエリを実行することを避けます。

休止状態と Windows Phone アプリのライフサイクルの詳細については、「Windows Phone 8 のアプリのアクティブ化および非アクティブ化」を参照してください。

廃棄

アプリが廃棄されると、基盤のデータベース接続が閉じられます。廃棄後に以前の状態に戻るには、アプリで廃棄される前に実行したすべてのクエリをやり直す必要があります。

インデックス作成

主キーとして指定されたデータベース列に対して、インデックスが自動的に作成されます。その後に、テーブルで作成される追加のインデックスは、セカンダリ インデックスと呼ばれます。セカンダリ インデックスは、結果の並べ替え順序を決定するために使われるプロパティなど、エンティティのよくクエリされるプロパティに対して使用する必要があります。

よく並べ替えられるプロパティには、適切な並べ替え順序 (昇順または降順) を使用する必要があります。既定で、インデックスは昇順の並べ替え順序になります。Index 属性で、明示的に並べ替え順序を指定するには、列名の後に ASC または DESC を付けて、それぞれ昇順または降順の並べ替えを指定します。たとえば、次のエンティティ属性は、OrderID 列を昇順の並べ替え順序で、Quantity 列を降順の並べ替え順序でインデックスを指定します。

[Index(Column=”OrderID ASC, Quantity DESC”)]

コンパイル済みクエリ

既定で、実行時にクエリが実行されるたびに、LINQ to SQL は LINQ 式ツリーを対応する Transact-SQL ステートメントに変換します。高い頻度で実行されるクエリ (たとえば、この ID でレコードを検索する) の場合、毎回対応する Transact-SQL を生成するオーバーヘッドは著しく無駄です。この非効率性を避けるため、コンパイル済みのクエリを使用できます。コンパイル済みのクエリはパラメーター化された Transact-SQL ステートメントを事前に生成し、さまざまな値で再利用できます。

フォアグラウンドとバックグラウンドの相互作用

Windows Phone 上のローカル データベースは、フォアグラウンド アプリと関連付けられたバックグラウンド エージェントの両方による同時使用をサポートします。このシナリオでは、次のことを考慮することが重要です。いずれかのデータベース クライアント (フォアグラウンド アプリまたはライブ エージェント) が何らかのデータベース操作の実行中に、突然終了した場合、他の接続が無効になり、再確立が必要になることがあります。この場合、データベースの他のクライアントが SqlCeException 例外を受け取ります。このような例外を受け取った場合、アプリは既存の DataContext オブジェクトを破棄し、それらを再作成する必要があります。

ヒントヒント:

異なるスレッド間で、ローカル フォルダーへのアクセスを同期するには、Mutex クラスを使用することをお勧めします。mutex は、1 つのスレッドだけに対して共有リソースへの排他アクセス権を付与します。1 つ目のスレッドがミューテックスを取得すると、ミューテックスを取得しようとしている 2 つ目のスレッドは、最初のスレッドがミューテックスを解放するまで中断されます。たとえば、mutex を使用して、フォアグラウンドで実行されている Windows Phone アプリとバックグラウンド エージェントの間でフォルダーへのアクセスを同期できます。

リソースの制約

フォアグラウンド アプリは、デバイス リソースのかなりの部分 (具体的には最大 90 MB の RAM) を消費します。逆に、バックグラウンドで実行するライブ エージェントは大きく制約されます。一般に、アプリのバックグラウンド動作を分割して、限られたリソースしか使用しない、独立して実行可能な管理しやすい小さいチャンクにすることを目標にしてください。アプリでデータベースを使用する場合、小さいバッチでクエリと更新の動作を実行することを意味します。たとえば RSS リーダー アプリの場合、データベース内のすべてのフィードを処理に先立って具現化するのではなく、記事の更新の動作をフィードごとにキャッシュで分割すると良いでしょう。この方法により、アプリで一度にオブジェクトに具現化されるデータベース レコード数を常に制限することができます。これにより、エージェントがプラットフォームによって設定されたメモリ制限を超えることを避けられます。

表示: