Статьи

Новый SQLite Wrapper для WP8 и Windows 8

Этот пост является второй частью серии о новой оболочке SQLite для Windows Phone 8 и Windows 8,  первую часть вы можете прочитать здесь .

Один из самых больших недостатков библиотеки sqlite-net для Windows 8 и Windows Phone 8 заключается в том, что она не поддерживает отношения: отсутствует атрибут, который можно использовать для пометки свойства класса как внешнего ключа. Кроме того, sqlite-net предлагает несколько методов для ручного выполнения оператора SQL, но проблема в том, что он не предлагает способа перебора результатов, но всегда ожидает конкретного объекта в ответ.

Давайте возьмем очень распространенную ситуацию в качестве примера: в базу данных с    таблицей People, которую мы создали в предыдущем посте, мы хотим добавить новую таблицу с именем  Orders , чтобы отслеживать заказы, сделанные пользователями. С помощью sqlite-net мы создали бы два класса: один для сопоставления  таблицы People и один для сопоставления  таблицы Orders  . Тогда мы могли бы выполнить ручной запрос аналогичным образом:

private async void OnExecuteJoin(object sender, RoutedEventArgs e)
{
   SQLiteAsyncConnection conn = new SQLiteAsyncConnection(Path.Combine(ApplicationData.Current.LocalFolder.Path, "people.db"), true);
   string query = "SELECT * FROM People INNER JOIN Orders ON Orders.PersonId = People.Id";
   List<Person> personOrders = await conn.QueryAsync<Person>(query);
}

В чем проблема с подходом sqlite-net? Это, как вы можете видеть, когда вы вызываете метод QueryAsync (),  он требует  параметра <T>  , который является типом, который вы ожидаете в результате запроса. Sqlite-net автоматически десериализует результат и предоставит вам объект, готовый для использования в вашем приложении. Умный, не так ли? Проблема в том, что этот подход не работает, когда у нас есть отношения: пример кода, который вы видите, неверен, потому что, когда мы создали отношения между таблицами  People  и  Orders,  мы больше не ожидаем получить в результате ,   объект Person или  Заказ объект, но сочетание их обоих. Обходной путь в этом случае — создать третий класс, например PeopleOrders , который будет содержать все свойства   класса People в сочетании со свойствами   класса Orders . Вдруг это звучит не так умно, не так ли? Улыбка

Вместо этого с помощью этой новой оболочки мы сможем поддержать этот сценарий, поскольку мы можем просто выполнять итерации по строкам, возвращаемым запросом, и выбирать нужные данные с помощью  объекта Statement . Единственным недостатком является то, что нам придется выполнять большую часть ручной работы: нам придется создавать наши объекты с нуля, поскольку нет автоматической сериализации и десериализации данных.

Давайте посмотрим, шаг за шагом, как поддерживать отношения с новой оболочкой. Мы будем повторно использовать знания, которые  мы узнали в предыдущем посте .

Давайте обновим базу данных

Первое, что нужно сделать, это создать новую таблицу, в которой будет храниться новая  таблица Orders  . Давайте посмотрим код:

private async void OnCreateDatabaseClicked(object sender, RoutedEventArgs e)
{
    Database database = new Database(ApplicationData.Current.LocalFolder, "people.db");
 
    await database.OpenAsync();
 
    string query = "CREATE TABLE PEOPLE " +
                   "(Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                   "Name varchar(100), " +
                   "Surname varchar(100))";
 
    await database.ExecuteStatementAsync(query);
        string query2 = "CREATE TABLE ORDERS " +
                        "(Id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
                        "Description VARCHAR(200)," +
                        "Amount INTEGER," +
                        "PersonId INTEGER, " +
                        "FOREIGN KEY(PersonId) REFERENCES People(Id) ON DELETE CASCADE)";
 
        await database.ExecuteStatementAsync(query2);
}

Таблица  People  такая же, как мы видели  в предыдущем постеПриказы  таблица содержит некоторые столбцы для хранения информации о порядке (описание, количестве и т.д.) , а также колонке для управления отношениями, которые будут выступать в качестве внешнего ключа. В частности, мы добавляем столбец с именем PersonId , в котором будет храниться идентификатор пользователя, который сделал заказ, взятый из  столбца  Id  таблицы People  . Мы также определяем, что этот столбец является внешним ключом и что, если мы удаляем пользователя, все его заказы будут также удалены (с помощью  оператора ON DELETE CASCADE  ). Для определения ключа мы используем следующее утверждение:

ИНОСТРАННЫЙ КЛЮЧ (PersonId) ЛИТЕРАТУРА Люди (Id)

это означает, что   столбец  PersonId  таблицы Orders будет содержать ссылку на  столбец  Id  таблицы People .

Управлять заказами

Теперь мы готовы начать использовать отношения и добавить заказ, сделанный пользователем  Matteo Pagani , у которого  Id  (который был автоматически сгенерирован) равен 1 (мы добавили этого пользователя, используя код из предыдущего поста).

private async void OnInsertOrderClicked(object sender, RoutedEventArgs e)
{
    Database database = new Database(ApplicationData.Current.LocalFolder, "people.db");
 
    await database.OpenAsync();
 
    string query = "INSERT INTO ORDERS(Description, Amount, PersonId) VALUES (@Description, @Amount, @PersonId)";
    Statement statement = await database.PrepareStatementAsync(query);
    statement.BindTextParameterWithName("@Description", "First order");
    statement.BindIntParameterWithName("@Amount", 200);
    statement.BindIntParameterWithName("@PersonId", 1);
 
    await statement.StepAsync();
}

Если вы читали мой предыдущий пост, в этом коде не должно быть ничего особенного: мы выполняем запрос вставки и добавляем в качестве параметров описание заказа, сумму заказа и идентификатор лица, сделавшего заказ. В конце концов, мы выполняем запрос , используя StepAsync ()  метод  Statement  объекта.

Теперь пришло время извлечь данные: давайте сделаем оператор соединения, чтобы получить все заказы с информацией о пользователе, который их сделал, как мы видели в первом примере с sqlite-net (тот, который не был работает  Улыбка).

private async void GetAllOrdersClicked(object sender, RoutedEventArgs e)
{
    Database database = new Database(ApplicationData.Current.LocalFolder, "people.db");
 
    await database.OpenAsync();
 
    string query = "SELECT * FROM ORDERS INNER JOIN PEOPLE ON ORDERS.PersonId=PEOPLE.Id";
 
    Statement statement = await database.PrepareStatementAsync(query);
    statement.EnableColumnsProperty();
 
    while (await statement.StepAsync())
    {
        MessageBox.Show(string.Format("{0} by {1} {2}", statement.Columns["Description"],
                                      statement.Columns["Name"], statement.Columns["Surname"]));
    }
 
}
?

Запрос точно так же: какие изменения в том , что, в настоящее время, мы можем перебирать результаты так, благодаря  Statement  объекта и  StepAsync ()  метод мы можем извлечь все значения которые нам нужны (Описание , Имя и фамилия) и отображать его с помощью MessageBox. В реальном приложении, возможно, мы бы заполнили коллекцию данных, которые мы бы отобразили в приложении, например, используя ListBox или LongListSelector. Просто обратите внимание, что я включил функцию свойства столбцов (с помощью метода EnableColumnsProperty ()  ), чтобы мы могли напрямую обращаться к столбцам, используя их имена в качестве индекса   коллекции Columns .

В конце

В последних двух статьях мы увидели другой подход к использованию SQLite в приложении для Windows Phone 8 или Windows 8: с sqlite-net у нас есть подход, аналогичный тому, который мы изучили с SQL CE в Windows Phone. Благодаря мощности LINQ, выполнение операций с базой данных действительно просто, и, как и с любым другим ORM, вы можете продолжать думать, используя объектный подход, вместо того, чтобы иметь дело с SQL. Но вся эта гибкость имеет свою цену: самым большим ограничением в настоящее время с sqlite-net является то, что почти невозможно управлять отношениями. С другой стороны, благодаря новой оболочке от Microsoft вы получаете полный контроль благодаря великолепной поддержке ручных операторов SQL; в сложных приложениях, где вам приходится иметь дело с множеством таблиц и взаимосвязей, это большой профессионал, и он может помочь вам также в тонкой настройке производительности;с другой стороны, вам придется иметь дело с ручными запросами даже для самых простых, таких как создание таблицы или добавление некоторых данных. Вам решать (и проекту, над которым вы работаете) выбирать, какая обертка лучше для вас!