Этот пост является второй частью серии о новой оболочке 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; в сложных приложениях, где вам приходится иметь дело с множеством таблиц и взаимосвязей, это большой профессионал, и он может помочь вам также в тонкой настройке производительности;с другой стороны, вам придется иметь дело с ручными запросами даже для самых простых, таких как создание таблицы или добавление некоторых данных. Вам решать (и проекту, над которым вы работаете) выбирать, какая обертка лучше для вас!