В третьем посте из серии SQLite-Net Extensions мы рассматриваем последний тип отношений – один-ко-многим (и наоборот – многие-к-одному).
Один ко многим, многие к одному
Отношения «один ко многим» обычно используются для отношений «родители-дети» или «целые элементы». Классические примеры: автобус и пассажиры, документ и элементы и т. д.
Отношение «один ко многим» означает, что объект объект знает о своих дочерних объектах, а ссылающиеся объекты имеют ссылку (внешний ключ) на своего родителя (но не обязательно знают об этом).
С другой стороны, противоположное отношение «один ко многим» – это «многие к одному». В этом случае многопользовательская сущность имеет ссылку на своего родителя и знает об этом, но односторонняя сущность не обязательно знает о своих дочерних элементах (по крайней мере, не напрямую).
Я использовал глагол “знать” несколько раз, так что пришло время объяснить что я имею в виду. «Зная» о другом объекте отношений, я понимаю, что имею в виду ссылку на него. Это означает, что, например, в отношениях «многие к одному» один объект не имеет отношения к своим дочерним.
Однако в большинстве случаев нам хотелось бы иметь гибрид отношений один-ко-многим и многие-к-одному. Я назову это один ко многим с инверсией. Мы хотим, чтобы оба родителя знали о своих детях, и каждый ребенок знал о своих родителях.
В этом посте мы рассмотрим один-ко-многим без реверсии и один-ко-многим с реверсией, так как он также включает отношения многие-к-одному, чтобы вы могли получить исчерпывающий обзор. Мы увидим это на примере сущностей Employee и Duty *. Каждый сотрудник имеет список своих обязанностей, в то время как каждая отдельная обязанность назначается только одному сотруднику.
Один ко многим без инверсии
Во-первых, давайте смоделируем такие отношения. Теперь мы можем преобразовать его в классы C #:
[Table("Employees")]
public class Employee
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
[OneToMany]
public List<Duty> Duties { get; set; }
}
В классе Employee (родитель, один объект отношения) мы определяем коллекцию дочерних элементов, украшенную атрибутом OneToManyAttribute. Типы коллекций, поддерживаемые на момент написания этой статьи SQLite-Net Extensions, это List и Array и могут использоваться по вашему усмотрению.
Давайте теперь посмотрим, как выглядит дочерняя сущность (Duty):
[Table("Duties")]
public class Duty
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Description { get; set; }
public DateTime Deadline { get; set; }
[ForeignKey(typeof(Employee))]
public int EmployeeId { get; set; }
}
В классе Duty (дочерний, многоконечные отношения) нам нужно определить внешний ключ для родительской сущности. Для этого мы создаем свойство, представляющее его (EmployeeId), декорируя его с помощью ForeignKeyAttribute, дополнительно указывая тип родительской ссылки (Employee).
Вот и все. Мы уже можем использовать его в нашем коде:
var db = new SQLiteConnection(new SQLitePlatformAndroid(), Constants.DbFilePath);
db.CreateTable<Employee>();
db.CreateTable<Duty>();
var employee = new Employee
{
Name = "Andrew",
LastName = "Programmer"
};
var duty1 = new Duty()
{
Description = "Project A Management",
Deadline = new DateTime(2017, 10, 31)
};
var duty2 = new Duty()
{
Description = "Reporting work time",
Deadline = new DateTime(2022, 12, 31)
};
db.Insert(employee);
db.Insert(duty1);
db.Insert(duty2);
employee.Duties = new List<Duty> {duty1, duty2};
db.UpdateWithChildren(employee);
var employeeStored = db.GetWithChildren<Employee>(employee.Id);
Здесь нет ничего сложного. Что нас интересует, так это то, как employeeStored выглядит в итоге:
Как видите, метод GetWithChildren возвратил объект типа Employee с надлежащим образом извлеченной коллекцией Duties (содержащей две обязанности, назначенные ранее сотруднику). Более того, у каждого дочернего элемента есть свой внешний ключ (EmployeeId), автоматически извлекаемый из БД – здесь нет никаких дополнительных затрат, это просто поле внешнего ключа, хранящееся в той же таблице базы данных SQLite (Duties).
Один-ко-многим с инверсией
(один-ко-многим + многие-к-одному)
Как и ранее, давайте сначала посмотрим, как меняется диаграмма классов после добавления инверсии:
Что изменилось, так это то, что теперь у каждого Duty есть свойство типа Employee.
Чтобы реализовать вышеприведенную диаграмму классов и сделать так, чтобы каждый дочерний объект (в нашем случае, каждый Duty) знал о своем родителе (имея ссылку на ответственного сотрудника), нам нужно только добавить следующее свойство в класс модели Duty:
[ManyToOne]
public Employee Employee { get; set; }
поэтому класс модели в итоге выглядит следующим образом:
[Table("Duties")]
public class Duty
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Description { get; set; }
public DateTime Deadline { get; set; }
[ForeignKey(typeof(Employee))]
public int EmployeeId { get; set; }
[ManyToOne]
public Employee Employee { get; set; }
}
Как вы можете видеть, мы только что создали отношение многие-к-одному, используя ManyToOneAttribute. Таким образом, в настоящее время у нас есть гибрид обоих типов отношений в рамках двух моделей.
Вызовы в нашем коде не нужно менять вообще. Теперь сущность employeeStored после инициализации тем же методом GetWithChildren, что и ранее, для каждой обязанности в дополнение к полю EmployeeId также содержит свойство Employee, надлежащим образом извлеченное SQLite-Net Extensions:
Опять же, здесь нет никаких дополнительных данных, потому что при извлечении сущности Employee из базы данных у нас уже есть, поэтому операция инициализации сущности Employee в каждой обязанности, содержащейся в коллекции Duties, не требует больше запросов к базе данных.
Резюме
Сегодня мы увидели, как моделировать и использовать отношения «один ко многим» (с инверсией и без нее) в базе данных SQLite с использованием расширений SQLite-Net. Автоматическая инициализация односторонних или многоцелевых объектов с помощью ORM чрезвычайно полезна при работе с такими объектами в нашем приложении. Количество кода для написания также минимально.
Не забудьте зайти на блог, там бывают полезные записи, которые я не публикую тут!
А чтобы быть в курсе последних моих постов, подписывайтесь на мой канал в Telegram.