SQLite-Net Extensions один к многим

В третьем посте из серии 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 чрезвычайно полезна при работе с такими объектами в нашем приложении. Количество кода для написания также минимально.