WindowsおよびWindows Phone JavaScriptアプリケーションでのSQLiteの使用





Windows Phone 8.1の新機能は、 JavaScriptとWindows 8.1で記述されたアプリケーションを作成および実行できることです 。 ただし、Windows Phone 8.1のアプリケーションで使用可能なAPIの仕様にはいくつかの違いがあります。 これらの違いの1つは、電話にIndexedDBがないことです。 これは、構造化ストレージを必要とする汎用アプリケーションのJavaScript開発者にとって課題です。 この記事では、JavaScriptからSQLiteを使用できるWinRTコンポーネントを作成する方法を説明します。 また、サンプルアプリケーションも用意しています。



:以下は、WinRTでSQLiteをラップする2つの既存のプロジェクトです。 独自のラッパーを記述する代わりに、それらを使用できます。 ラッパーを作成する前に、ラッパーが必要な機能を提供しているかどうか、およびライセンスが適切かどうかを確認してください。 この投稿で説明したソリューションは、主にライセンスの問題を回避するために生まれました。





計画



ユニバーサルWindows JavaScriptアプリケーションでSQLiteを使用する方法を学ぶために、次の計画に固執します。



  1. JavaScriptで既存のユニバーサルWindowsアプリケーションのVisual Studioプロジェクトを開きましょう。
  2. Visual StudioのSQLite拡張機能をインストールします。
  3. ユニバーサルアプリケーションプロジェクト用のWinRTコンポーネントを作成します。
  4. SQLiteをラップするためのコードを記述します。
  5. SQLiteのWinRTコンポーネントを使用して、一般的なアプリケーションコードを記述します。


アプリケーションの実装を調べ、SQLiteを使用するためにIndexedDBコードを変更することに注意を払って、この計画に従います。 基礎として、Windows 8.1用のIndexedDBを取り上げ、ユニバーサルアプリケーションにし、以下で説明する手順を実行しました。



Visual Studio用のSQLite拡張機能をインストールする



SQLite開発チームは、Visual Studio SQLite for Windows Runtime(Windows 8.1)の拡張機能をリリースし、SQLiteをWindows 8.1アプリケーションに簡単に追加できるようにしました。 上記リンクに従って、[ダウンロード] リンクをクリックし、VSIXファイルを開いてVisual Studioに拡張機能をインストールします。



SQLite開発チームは、VS- SQLite for Windows Phone 8.1の別の拡張機能もリリースしました。 同じ手順に従って、拡張機能をインストールします。



ユニバーサルアプリケーションプロジェクト用のWinRTコンポーネントの作成



SQLiteはCで記述されており、JavaScriptアプリケーションで使用するには、WinRTコンポーネントでSQLite APIをラップする必要があります。



Visual Studioでアプリケーションを開き、ユニバーサルアプリケーション用の新しいWindowsランタイムコンポーネントプロジェクトを追加します。これは、Visual C ++>ストアアプリ>ユニバーサルアプリにあります。 Windows、Windows Phoneプロジェクト、および共有ファイルは、新しいWinRTコンポーネントのソリューションで作成されます。



新しいWinRTコンポーネントを使用するには、アプリケーションプロジェクトからWinRTコンポーネントプロジェクトへのリンクを追加する必要があります。 Windows 8.1プロジェクトでは、Windows 8.1用のWinRTコンポーネントへのリンクを追加し、Windows Phone 8.1プロジェクトでは、Windows Phone用のWinRTコンポーネントへのリンクをそれぞれ追加します。



これで、アプリケーションはWinRTコンポーネントを使用できますが、それでもSQLite拡張機能は使用しません。 Windows 8.1およびWindows Phone 8.1のWinRTコンポーネントにSQLiteリンクを追加します。 拡張機能は、Windows(電話)8.1の拡張機能の[リンクの追加]ダイアログにあります。



SQLiteラッパーコードの記述



C ++ / CX WinRTコンポーネントの作成の詳細については、「 C ++ドキュメントでのWindowsランタイムコンポーネント作成」および「 Visual C ++言語リファレンス(C ++ / CX)」リンクを参照してください。 最小限の機能を備えたWinRTコンポーネントを作成し、JavaScriptのほとんどのタスクで使用できるようにします。



この場合、アプリケーションはデータベースに接続し、テーブルを作成し、データを挿入し、トランザクションで操作を実行する必要があります。 この例に必要なスキーマは非常に単純であるため、WinRTラッパーにはデータベースオブジェクトのみが含まれ、データベースオブジェクトを開いたり閉じたりして、SQLステートメントを実行できます。 データの追加を簡素化するために、SQLステートメントを使用した通信パラメーターをサポートしています。 要求されたデータを取得するには、実行可能メソッドから行オブジェクトの配列を返します。 すべてのメソッドは非同期であるため、データベースの使用中にアプリケーションUIスレッドをブロックしません。



JavaScriptでは、一度に1つまたはトランザクションでリクエストを非同期的に実行し、リクエストの結果をJavaScriptオブジェクトに変換できるようにするいくつかの関数を実装します。



独自のプロジェクトでは、追加のSQLite APIが必要になる場合があります。 この例は、高度なSQLite機能を必要としない単なるデモです。



実装の詳細



以下は、SQLiteのWinRTコードです。



C ++ / CX



SQLiteライブラリAPIはCで記述されており、主にUTF-8 char *を使用し、エラーが発生するとエラーコードを返します。 対照的に、WinRTは通常、エラーが発生するとUTF-16プラットフォーム::文字列を使用し、例外を返します。 util。* Filesに、 ValidateSQLiteResultを実装しました。これは、SQLite関数から返されたエラーコードをWinRT例外に変換するか、エラーが発生しない場合に戻り値を渡します。 また、util。*ファイルには、UTF-8 文字列std :: stringおよびUTF-16とPlatform :: String文字列型の間で変換するための2つの関数があります。



Database。*ファイルでは、いくつかのメソッドを持つWinRTのDatabaseクラスを実装します。 Database.hクラスのコードは次のとおりです。



public ref class Database sealed { public: static Windows::Foundation::IAsyncOperation<Database^> ^OpenDatabaseInFolderAsync(Windows::Storage::StorageFolder ^databaseFolder, Platform::String ^databaseFileName); virtual ~Database(); Windows::Foundation::IAsyncOperationWithProgress< Windows::Foundation::Collections::IVector<ExecuteResultRow^>^, ExecuteResultRow^> ^ExecuteAsync(Platform::String ^statementAsString); Windows::Foundation::IAsyncOperationWithProgress< Windows::Foundation::Collections::IVector<ExecuteResultRow^>^, ExecuteResultRow^> ^BindAndExecuteAsync(Platform::String ^statementAsString, Windows::Foundation::Collections::IVector<Platform::String^> ^parameterValues); private: Database(); void CloseDatabase(); void EnsureInitializeTemporaryPath(); void OpenPath(const std::string &databasePath); static int SQLiteExecCallback(void *context, int columnCount, char **columnNames, char **columnValues); sqlite3 *database; };
      
      





静的メソッドOpenDatabaseInFolderAsyncは、 データベースオブジェクトを作成するための唯一のパブリックメソッドです。 この非同期メソッドは、IAsyncOperation <Database ^> ^ createdまたはpublic Databaseオブジェクトを返します。 実装では、SQLiteのドキュメントに記載されているとおりに SQLiteの一時パスが構成されていることを確認してから、util。*の関数を使用してsqlite3_open_v2を呼び出します。 PPL create_asyncを使用して非同期操作を実装します。



Database.cppファイルのOpenDatabaseInFolderAsyncメソッドの定義は次のとおりです。



 Windows::Foundation::IAsyncOperation<Database^> ^Database::OpenDatabaseInFolderAsync(Windows::Storage::StorageFolder ^databaseFolder, Platform::String ^databaseFileName) { return create_async([databaseFolder, databaseFileName]() -> Database^ { Database ^database = ref new Database(); string databasePath = PlatformStringToUtf8StdString(databaseFolder->Path); databasePath += ""; databasePath += PlatformStringToUtf8StdString(databaseFileName); database->OpenPath(databasePath); return database; }); }
      
      





データベース:: ExecuteAsyncも非同期であり、今回はIAsyncOperationWithProgress <IVector <ExecuteResultRow ^> ^、ExecuteResultRow ^>を返します。非同期結果は、実行可能なSQLステートメントによって要求されたExecuteResultRowsのベクトルであり、さらに同じ要求行を含む実行通知を提供しますただし、同時選択の場合にのみ提供されます。 コールバックを使用するsqlite3_execを呼び出して、クエリの結果を返します。 以下は、Database.cppファイルのExecuteAsyncおよびSQLiteExecCallbackメソッドの実装です。



 struct SQLiteExecCallbackContext { Windows::Foundation::Collections::IVector<ExecuteResultRow^> ^rows; Concurrency::progress_reporter<SQLite::ExecuteResultRow^> reporter; }; Windows::Foundation::IAsyncOperationWithProgress< Windows::Foundation::Collections::IVector<ExecuteResultRow^>^, ExecuteResultRow^> ^Database::ExecuteAsync(Platform::String ^statementAsString) { sqlite3 *database = this->database; return create_async([database, statementAsString](Concurrency::progress_reporter<SQLite::ExecuteResultRow^> reporter) -> Windows::Foundation::Collections::IVector<ExecuteResultRow^>^ { SQLiteExecCallbackContext context = {ref new Vector<ExecuteResultRow^>(), reporter}; ValidateSQLiteResult(sqlite3_exec(database, PlatformStringToUtf8StdString(statementAsString).c_str(), Database::SQLiteExecCallback, reinterpret_cast<void*>(&context), nullptr)); return context.rows; }); } int Database::SQLiteExecCallback(void *contextAsVoid, int columnCount, char **columnNames, char **columnValues) { SQLiteExecCallbackContext *context = reinterpret_cast<decltype(context)>(contextAsVoid); ExecuteResultRow ^row = ref new ExecuteResultRow(columnCount, columnNames, columnValues); context->rows->Append(row); context->reporter.report(row); return 0; }
      
      





SQLパラメーターのバインディングを確実にするために、Database :: ExecuteAsyncと同じ値を返すDatabase :: BindAndExecuteAsyncを実装しました。 データベース:: ExecuteAsyncは、SQLステートメントにバインドされる行のベクトルであるパラメーターを受け入れます。 IVector <String ^> ^パラメータが呼び出しスレッドにバインドされていることに注意してください。したがって、文字列のリストのコピーをstd :: vector <String ^>として作成します。 ラムダ式create_asyncで修正し、別のスレッドで使用できます。 なぜなら sqlite3_execはパラメーターバインディングを提供しません。sqlite3_prepare、sqlite3_bind、sqlite3_step、およびsqlite3_finalizeの一連の明示的な実装を実行します。



Database.cppファイルのBindAndExecuteAsyncの定義は次のとおりです。



 Windows::Foundation::IAsyncOperationWithProgress< Windows::Foundation::Collections::IVector<ExecuteResultRow^>^, ExecuteResultRow^> ^Database::BindAndExecuteAsync( Platform::String ^statementAsString, Windows::Foundation::Collections::IVector<Platform::String^> ^parameterValuesAsPlatformVector) { sqlite3 *database = this->database; //     ,   // IVector      std::vector<Platform::String^> parameterValues; for (unsigned int index = 0; index < parameterValuesAsPlatformVector->Size; ++index) { parameterValues.push_back(parameterValuesAsPlatformVector->GetAt(index)); } return create_async([database, statementAsString, parameterValues](Concurrency::progress_reporter<SQLite::ExecuteResultRow^> reporter) -> Windows::Foundation::Collections::IVector<ExecuteResultRow^>^ { IVector<ExecuteResultRow^> ^results = ref new Vector<ExecuteResultRow^>(); sqlite3_stmt *statement = nullptr; ValidateSQLiteResult(sqlite3_prepare(database, PlatformStringToUtf8StdString(statementAsString).c_str(), -1, &statement, nullptr)); const size_t parameterValuesLength = parameterValues.size(); for (unsigned int parameterValueIndex = 0; parameterValueIndex < parameterValuesLength; ++parameterValueIndex) { //   1 ValidateSQLiteResult(sqlite3_bind_text(statement, parameterValueIndex + 1, PlatformStringToUtf8StdString(parameterValues[parameterValueIndex]).c_str(), -1, SQLITE_TRANSIENT)); } int stepResult = SQLITE_ROW; while (stepResult != SQLITE_DONE) { stepResult = ValidateSQLiteResult(sqlite3_step(statement)); if (stepResult == SQLITE_ROW) { const int columnCount = sqlite3_column_count(statement); ExecuteResultRow ^currentRow = ref new ExecuteResultRow(); for (int columnIndex = 0; columnIndex < columnCount; ++columnIndex) { currentRow->Add( reinterpret_cast<const char*>(sqlite3_column_text(statement, columnIndex)), sqlite3_column_name(statement, columnIndex)); } results->Append(currentRow); reporter.report(currentRow); } } ValidateSQLiteResult(sqlite3_finalize(statement)); return results; }); }
      
      







ファイルにExecuteResultRow。*データベースへのクエリの結果を含むExecuteResultRowおよびColumnEntryを実装します。 これはWinRTでデータを使用するために必要であり、SQLite APIとの相互作用はありません。 ExecuteResultRowの最も興味深い部分は、Database :: * ExecuteAsyncのメソッドをどのように使用するかです。



Javascript



default.jsファイルでは、JavaScriptアプリケーションでWinRTコンポーネントの使用を簡素化するためのいくつかのメソッドを実装しています。



runPromisesInSerial関数は、一連の非同期ExecuteAsyncコマンドの実行を容易にするために、次々に実行されるPromiseおよびEnsureオブジェクトの配列を受け入れます。



 function runPromisesInSerial(promiseFunctions) { return promiseFunctions.reduce(function (promiseChain, nextPromiseFunction) { return promiseChain.then(nextPromiseFunction); }, WinJS.Promise.wrap()); }
      
      





executeAsTransactionAsync関数は、トランザクションを開き、関数を実行してから、トランザクションを閉じます。 唯一の興味深い点は、関数が非同期であることです。トランザクションを完了するには、非同期実行を待機して結果を取得する必要があります。 それでも正常な結果を返すか、エラー値を返すことを確認してください。



 function executeAsTransactionAsync(database, workItemAsyncFunction) { return database.executeAsync("BEGIN TRANSACTION").then(workItemAsyncFunction).then( function (result) { var successResult = result; return database.executeAsync("COMMIT").then(function () { return successResult; }); }, function (error) { var errorResult = error; return database.executeAsync("COMMIT").then(function () { throw errorResult; }); }); }
      
      





ExecuteStatementsAsTransactionAsyncbindAndExecuteStatementsAsTransactionAsyncは、以前の2つの機能を組み合わせて、クエリと結果の操作を容易にします。



 function executeStatementsAsTransactionAsync(database, statements) { var executeStatementPromiseFunctions = statements.map(function statementToPromiseFunction(statement) { return database.executeAsync.bind(database, statement); }); return executeAsTransactionAsync(database, function () { return runPromisesInSerial(executeStatementPromiseFunctions); }); } function bindAndExecuteStatementsAsTransactionAsync(database, statementsAndParameters) { var bindAndExecuteStatementPromiseFunctions = statementsAndParameters.map( function (statementAndParameter) { return database.bindAndExecuteAsync.bind(database, statementAndParameter.statement, statementAndParameter.parameters); }); return executeAsTransactionAsync(database, function () { return runPromisesInSerial(bindAndExecuteStatementPromiseFunctions); }); }
      
      





次に、これらの関数を使用してSQLクエリを非同期的かつ連続的に実行する方法を確認できます。



 SQLite.Database.openDatabaseInFolderAsync( Windows.Storage.ApplicationData.current.roamingFolder, "BookDB.sqlite").then( function (openedOrCreatedDatabase) { database = openedOrCreatedDatabase; return SdkSample.executeStatementsAsTransactionAsync(database, [ "CREATE TABLE IF NOT EXISTS books (id INTEGER PRIMARY KEY UNIQUE, title TEXT, authorid INTEGER);", "CREATE TABLE IF NOT EXISTS authors (id INTEGER PRIMARY KEY UNIQUE, name TEXT);", "CREATE TABLE IF NOT EXISTS checkout (id INTEGER PRIMARY KEY UNIQUE, status INTEGER);" ]); // ...
      
      





IndexedDBからSQLiteへの移行



移行の理由は、Windows 8.1上にIndexedDBを使用するアプリケーションがあり、それをユニバーサルアプリケーションにすることです。 これを実装するには、IndexedDBの代わりにWinRT SQLiteラッパーを使用するようにコードを変更する必要があります。



残念ながら、この状況で何をすべきかという簡単な答えはありません。 この例で説明するアプリケーションでは、生のSQLコントラクトを提供し、予備のスキーマを必要とし、Promiseオブジェクトとの非同期実行を表す通常のSQLテーブルを使用します。 一方、IndexedDBはJavaScriptオブジェクトを読み書きします。 Promiseとは異なり、SQLステートメントの使用に重点を置き、Eventオブジェクトを使用します。



サンプルアプリケーションで変換されたコードは、元のIndexedDBの例とは大きく異なります。 IndexedDBコードがたくさんある場合は、WinRTラッパーを作成して、そのインターフェイスがIndexedDBのように見えるようにすることができます。 アプリケーションデータベースコードが他のコードから十分に分離されているか、変換が容易であることを願っています。



追加資料



SQLiteライブラリ

Windowsランタイム用のSQLiteをダウンロードする

サンプルSQLite汎用JavaScriptアプリケーション

SQLite-WinRTの記事

Microsoft Virtual Academy(MVA)トレーニングコース

Visual Studio 2013の無料または試用版をダウンロードする



All Articles