Статьи

Lex.db: новое решение для хранения баз данных для Windows 8

Недавно в своем блоге я много говорил о решениях для баз данных, доступных в Windows Phone 8. Существует много различных способов управления базами данных, каждый из которых имеет свои плюсы и минусы. SQL CE с LINQ to SQL является наиболее мощным, но не поддерживается Windows 8, и сложно делиться кодом и базой данных с другими платформами. Поддержка SQLite является многообещающей, и движок очень быстрый, но на данный момент ему не хватает мощной библиотеки для управления данными, поэтому она немного ограничена.

Пожалуйста, поприветствуйте нового участника этого «конкурса»: Lex.db, интересное решение, разработанное Лексом Лавниковым (часто посещайте его блог, потому что он регулярно публикует обновления о проекте). Вы помните Стерлинг ? Это объектно-ориентированная база данных, которая использует файловую систему для структурированного хранения данных, чтобы ее можно было использовать с LINQ для выполнения запросов и операций, как обычная база данных. Самым большим преимуществом является то, что он доступен для многих технологий .NET, и он очень быстрый, особенно если вам просто нужно сохранить данные: поскольку он использует только файловую систему, он не добавляет накладных расходов на ядро ​​базы данных.

Lex.db основан на тех же принципах, но поддерживает также среду выполнения Windows: по этой причине его можно использовать не только с Windows Phone 8, но также с приложениями Windows Store и с полной платформой .NET. Как это работает? Lex.db может автоматически сериализовать данные, которые нам нужны для хранения в файловой системе, и хранить в памяти только ключи и индексы, необходимые для выполнения операций с файлом. По этой причине это действительно быстро и часто производительность выше, чем при использовании SQL Lite.

Какие минусы? На данный момент он по-прежнему не поддерживает Windows Phone 7, поэтому это не идеальный выбор, если вам нужно разделить уровень данных вашего приложения между двумя проектами для Windows Phone 7 и Windows Phone 8. Кроме того, это не настоящая база данных. решение: например, на данный момент отношения не поддерживаются, поэтому вам придется вручную управлять ими, если вам нужно.

Но, например, если вы собираетесь разрабатывать приложение To Do List (или любое другое приложение, которое должно хранить в хранилище набор данных), и вы хотите поделиться слоем данных с версией приложения для Магазина Windows, это действительно хорошее решение.

Давайте посмотрим, как использовать его в приложении Windows Phone (но код будет точно таким же для приложения Магазина Windows).

Первая настройка

Самый простой способ использовать Lex.Db в вашем проекте — это установить его с помощью NuGet: щелкните правой кнопкой мыши по своему проекту, выберите « Управление пакетами NuGet» и, используя встроенную поисковую систему, найдите и установите пакет с именем Lex.db.

После этого мы можем создать объект, который будет хранить информацию, которая будет храниться в файловой системе. В отличие от sqlite-net или LINQ to SQL, нам не нужно украшать классы специальными атрибутами: нам просто нужно определить класс. Давайте воспользуемся примером приложения ToDo List: в этом случае нам нужен класс для хранения всей информации об активности.

public class Activity
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public DateTime ExpireDate { get; set; }
}

Теперь, точно так же, как мы делаем с SQL CE или SQL Lite, нам нужно сообщить библиотеке, которая является структурой нашей базы данных. В нашем примере у нас будет один «стол» для хранения всех действий. Сначала мы объявляем новый объект DbIstance на странице, чтобы он мог использоваться любым методом класса:

private DbInstance db;
 
private async void LoadDatabase()
{
    db = new DbInstance("catalog");
    db.Map<Activity>()
      .Automap(x => x.Id);
 
    await db.InitializeAsync();
}

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

Затем, используя свободный подход, мы устанавливаем, какой объект нужно хранить с помощью метода Map <T> , где T — это класс, который мы собираемся сериализовать (в нашем примере, Activity ). В конце мы определяем, какой является первичным ключом таблицы, который будет использоваться для выполнения всех запросов, используя метод Automap : используя синтаксис лямбды, мы указываем, какое свойство нашей сущности является первичным ключом, в нашем примере Идентификатор собственности. Прежде чем перейти к выполнению операций, мы вызываем метод InitializeAsync () для объекта DbInstance , чтобы библиотека могла создать всю необходимую инфраструктуру для хранения данных. Это асинхронная операция, поэтому мы помечаем методКлючевое слово async, и мы используем ключевое слово await , чтобы дождаться, когда все настроено правильно, прежде чем делать что-то еще. Библиотека также предоставляет синхронные методы, но я всегда предлагаю вам использовать асинхронные методы, чтобы обеспечить лучший пользовательский опыт.

Теперь мы готовы сохранить некоторые данные:

private async void OnInsertDataClicked(object sender, RoutedEventArgs e)
{
 
    Activity activity = new Activity
                            {
                                Id = 1,
                                Title = "Activity",
                                Description = "This is an activity",
                                ExpireDate = DateTime.Now.AddDays(3)
                            };
 
    await db.Table<Activity>().SaveAsync(activity);
}

Синтаксис довольно прост: мы создаем новый объект Activity и передаем его методу SaveAsync в объекте Table <Activity> , который является отображением «таблицы», в которой хранятся наши данные. Просто, не правда ли? А если мы хотим вернуть наши данные? Вот пример:

private async void OnReadDataClicked(object sender, RoutedEventArgs e)
{
    Activity[] activities = await db.Table<Activity>().LoadAllAsync();
 
    foreach (var activity in activities)
    {
        MessageBox.Show(activity.Title);
    }
}

В этом примере мы извлекаем, используя асинхронный метод, все объекты Activity с помощью метода LoadAllAsync , который доступен с помощью свойства Table <T> базы данных. Поскольку первичный ключ таблицы действует также как индекс, мы также можем запросить таблицу, чтобы получить только элемент с определенным идентификатором, используя метод LoadByKeyAsync :

private async void OnReadDataClicked(object sender, RoutedEventArgs e)
{
    Activity activity = await db.Table<Activity>().LoadByKeyAsync(1);
 
    MessageBox.Show(activity.Title);
}

Пожалуйста, обратите внимание! Объект DbInstance напрямую предоставляет некоторые методы для выполнения этих базовых операций, таких как Save () и LoadAll () . Библиотека может понять, какой класс мы используем, и выполнить операцию с правильной таблицей. В любом случае, я предлагаю вам всегда получать доступ к данным, используя свойство Table <T>, потому что таким образом у вас есть доступ и к асинхронному методу, который, в противном случае, был бы недоступен.

Использование индексов

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

Сначала нам нужно изменить первую конфигурацию базы данных:

private async void LoadDatabase()
{
    db = new DbInstance("catalog");
    db.Map<Activity>()
      .Automap(x => x.Id)
      .WithIndex("Title", x => x.Title);
 
    await db.InitializeAsync();
}

Мы вызвали новый метод с именем WithIndex: в качестве параметра мы передаем имя нашего индекса ( в нашем случае — Title ) и, используя лямбда-выражение, являющееся свойством нашей сущности, которое будет отображено с помощью ключа. ,

Теперь мы можем выполнять запросы к свойству Title , используя тот же метод LoadAllAsync, который мы видели ранее:

private async void OnReadDataClicked(object sender, RoutedEventArgs e)
{
    List<Activity> activities = await db.Table<Activity>().LoadAllAsync("Title", "Activity");
 
    foreach (var activity in activities)
    {
        MessageBox.Show(activity.Description);
    }
}

Разница с предыдущим примером заключается в том, что теперь в метод LoadAllAsync () передаются два параметра: первый — это имя используемого индекса, а второй — искомое значение. Результатом этого кода является то, что коллекция действий будет содержать все элементы, свойство Title которых равно «Activity».

Что происходит под капотом?

Если мы хотим увидеть, что происходит под капотом, мы можем использовать такой инструмент, как Windows Phone Power Tools, чтобы исследовать хранилище нашего приложения. Мы увидим, что библиотека создала папку с именем Lex.db. Внутри вы найдете подпапку с именем базы данных, которую вы указали при создании первого объекта DbIstance: внутри нее вы найдете файл .data, который содержит реальные данные, и файл .key, который используется для индексации. Если вы попытаетесь открыть его в текстовом редакторе, таком как Блокнот, вы увидите содержимое данных, которые вы сохранили в приложении, а также некоторые другие специальные символы из-за кодировки, созданной механизмом сериализации библиотеки. образ

образ

Если вы хотите немного поэкспериментировать с Lex.Db, используйте следующую ссылку, чтобы загрузить пример проекта, который я создал.

Скачать пример проекта