В сегодняшнем посте мы рассмотрим, что такое SQLite-Net Extensions ORM и как его использовать для создания отношений «многие ко многим» в базе данных SQLite. Другие типы отношений будут описаны в отдельных постах.

Что такое SQLite-Net Extensions?

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

Однако, если вам нужно смоделировать какие-либо отношения в вашей базе, в SQLite.NET есть обертка, которая позволяет это делать — это SQLite-Net Extensions. Он в основном расширяет основные функциональные возможности SQLite.NET, добавляя элементы, которые позволяют легко обрабатывать отношения, включая один-к-одному, один-ко-многим, многие-к-одному и многие-ко-многим.

В этом посте мы увидим, как создать отношение «многие ко многим» с помощью этой библиотеки. Мне нужны были такие отношения, чтобы смоделировать связь между сущностями Person и Event в моем приложении.

Отношения «многие ко многим»

Давайте посмотрим на отношение многие ко многим на примере двух сущностей: Персона и Событие. Событие (сущность типа Event) может содержать ноль или более участников (сущности типа Person), в то время как персона может быть назначена на ноль или более событий. Это типичное отношение «многие ко многим», которое мы собираемся установить в нашей базе данных сейчас.

Устанавливаем SQLite-Net Extensions

Если вы ранее использовали SQLite.NET в своем проекте — сначала удалите его. Я не делал этого до того, как начал использовать SQLite-Net Extensions, и у меня много проблем с тем, что Visual Studio неправильно обрабатывала мои ссылки. SQLite-Net Extensions — это оболочка для SQLite.NET, поэтому она уже содержит эту библиотеку и дополнительно расширяет ее, добавляя некоторые дополнительные функции для обработки отношений.

SQLite-Net Extensions могут быть установлены как пакет Nuget в ваше решение. В зависимости от версии, которую вы хотите использовать, выполните соответствующую команду в консоли диспетчера пакетов в Visual Studio:

синхронный:

Install-Package SQLiteNetExtensions -Version 2.1.0

асинхронный:

Install-Package SQLiteNetExtensions.Async -Version 2.1.0

Определяем модельные классы

Затем нам нужно определить классы модели Person и Event и установить отношения между ними. Ниже вы можете найти код обоих классов:

// Person class modelling People table
[Table("People")]
public class Person
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }

    public string Name { get; set; }

    public string LastName { get; set; }

    public string PhoneNumber { get; set; }

    public string Email { get; set; }

    [ManyToMany(typeof(PersonEvent))]
    public List<Event> Events { get; set; }
}


// Event class modelling Events table
[Table("Events")]
public class Event
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }

    public string Name { get; set; }
    public DateTime Date { get; set; }
    public string Place { get; set; }

    [ManyToMany(typeof(PersonEvent))]
    public List<Person> Participants { get; set; }
}

Как вы можете видеть, модели выглядят почти так же, как сущности БД SQLite.NET, со следующими исключениями:

  • ManyToManyAttribute — на обоих объектах вы можете заметить, что этот атрибут определен. В классе модели Person я украшаю им коллекцию Events, тогда как в классе модели Event я украшаю им коллекцию участников.
  • PersonEvent — вы могли заметить, что в качестве аргумента для атрибута ManyToManyAttribute в обеих моделях я передал тип PersonEvent. Как вы, возможно, знаете, при моделировании отношений «многие ко многим» нам нужна промежуточная сущность, чтобы хранить такие отношения в таблицах базы данных. Классический пример отношений студенты и курсы:

Нам также необходимо определить такую промежуточную сущность в нашем коде.

Реализация класса промежуточной модели PersonEvent выглядит следующим образом:

public class PersonEvent
{
    [ForeignKey(typeof(Person))]
    public int PersonId { get; set; }

    [ForeignKey(typeof(Event))]
    public int EventId { get; set; }
}

Благодаря атрибутам PrimaryKey, определенным для сущностей Person и Event, ORM сможет определить, к каким первичным ключам относятся внешние ключи в этой промежуточной таблице.

В атрибуте ManyToManyAttribute, кроме типа промежуточной сущности, вы можете установить CascadeOperations, который указывает, как следует обрабатывать каскад при работе с сущностями (например, следует ли выполнять операцию каскадного удаления при удалении одной из сторон отношения).

Вставка и чтение данных

Как только наши модельные классы определены, мы можем записывать и читать данные с отношением «многие ко многим». Следующий код представляет простой способ создать нового человека и назначить его событию:

var db = new SQLiteConnection(new SQLitePlatformAndroid(), Constants.DbFilePath);
db.CreateTable<Person>();
db.CreateTable<Event>();
db.CreateTable<PersonEvent>();

var event1 = new Event
{
    Name = "Volleyball",
    Date = new DateTime(2017, 06, 18),
    Place = "Sports hall"
};

var person1 = new Person
{
    Name = "A",
    LastName = "B",
    PhoneNumber = "123456789"
};


db.Insert(person1);
db.Insert(event1);

person1.Events = new List<Event> { event1 };
db.UpdateWithChildren(person1);

var personStored = db.GetWithChildren<Person>(person1.Id);
var eventStored = db.GetWithChildren<Event>(event1.Id);

Строки 1-4 содержат инициализацию базы данных  и создание всех трех таблиц в базе данных.

Строки 6-18 — это просто создание объектов Персона и Событие, наполненных самыми основными деталями.

В строках 21 и 22 мы сначала сохраняем наши сущности person1 и event1 отдельно, потому что для установления отношений нам нужны первичные ключи тех сущностей, которые назначаются базой данных при сохранении. Это также можно упростить с помощью рекурсивных операций — более подробную информацию можно найти в официальной документации ORM.

После этого мы назначаем только что созданного человека на событие (строка 24), а затем наступает самая ракетостроительная часть:

db.UpdateWithChildren(person1);

Этот метод следующее: он обновляет человека со всеми его дочерними элементами (коллекция Events). Это сделает отношения установленными.

Чтобы доказать это, в строках 27 и 28 мы можем проверить, заполнены ли коллекции отношений дочерними элементами в обеих сущностях, вызвав методы расширения GetWithChildren:

Person содержит Events

Событие, содержащее участников

Событие, содержащее участников

Вот как работает SQLite-Net Extensions ORM. Он не обеспечивает ленивую загрузку связанных сущностей — он просто добавляет / извлекает в / из базы данных именно то, о чем вы говорите. Здесь существует ограничение: если вы обращаетесь к коллекции Person.Events, вы можете видеть события, с которыми связан этот человек, но если вы обращаетесь к Person.Events [0], вы не увидите всех людей, зарегистрированных для этого события.

Резюме

SQLite-Net Extensions — это ORM, который является оболочкой для классической библиотеки SQLite.NET. Он добавляет методы расширения / атрибуты для обработки отношений в базе данных SQLite. Он не предоставляет никакого механизма отложенной загрузки, вместо этого он предоставляет методы для получения / сохранения сущностей вместе со своими дочерними элементами (связанными сущностями). Он легкий и довольно простой в реализации, поэтому для небольших решений, таких как мобильные приложения, я полностью предпочитаю писать и поддерживать запросы SQL непосредственно в C # для обработки отношений.

В следующих статьях из серии о SQLite-Net Extensions ORM я представлю вам другие типы отношений, которые предлагает ORM. Оставайтесь с нами 🙂