Предыдущие статьи:
В прошлой статье мы сделали CRUD-сервисы для работы с сущностями Person (человек) и DisabilitySheet (больничный). Теперь надо сделать CRUD-сервисы для AppointmentOrder (приказ о назначении) и HolidayOrder (приказ об отпуске).
Базовый сервис приказов
Поскольку у этих двух сущностей есть общие поля, мы создали для них общий базовый класс BaseOrder (базовый приказ). Поскольку иерархия CRUD-сервисов повторяет иерархию сущностей, то мы должны сначала сделать сервис для BaseOrder и уже потом - для этих двух сущностей. Сервис для BaseOrder будет работать с полями, общими для всех Order'ов.
Поскольку у BaseOrderService будет несколько наследников для разных сущностей (AppointmentOrderService и HolidayOrderService), то мы должны сделать у него универсальные типы для сущностей и для фильтра, как это сделали в BaseEntityService:
Конкретные значения для TEntity и TListFilter будут заданы в сервисах-наследниках AppointmentOrderService и HolidayOrderService.
Красным горит ошибка "Abstract inherited member ... is not implemented" ("Абстрактный унаследованный член класса не реализован"). Это значит, что в базовом классе был абстрактный метод FilterList(), который наш класс получил в силу наследования. Поскольку этот метод абстрактный, то мы должны либо написать его реализацию, либо пометить наш класс абстрактным. Подумаем, каким должен быть метод FilterList() для BaseOrder. Будет ли в нём какая-нибудь логика? По каким полям можно фильтровать все сущности, унаследованные от BaseOrder? У BaseOrder есть такие поля:
Соответственно, мы можем фильтровать приказы по номеру, дате, сотруднику и автору. Значит, в нашем FilterList() нужно написать логику фильтрации по этим полям, а в FilterList() классов-наследников - по новым полям, которые есть у наследников. Значит, FilterList() в BaseOrderService нужен, но это будет не окончательная его реализация - он будет также и в сервисах-наследниках AppointmentOrderService и HolidayOrderService.
Чтобы сделать метод, можно набрать его вручную, а можно поставить каретку для ввода текста на красную ошибку, нажать лампочку и выбрать "Implement missing members":
Теперь мы упёрлись в то, что нет гарантии, что у TListFilter будут все нужные нам поля (для фильтрации по номеру, дате, сотруднику и автору).
Наследование фильтров
Поскольку иерархия наследования сервисов повторяет иерархию наследования сущностей, логично сделать такую же иерархию наследования и для фильтров. Таким образом, мы создадим класс BaseOrderListFilter и унаследуем от него классы AppointmentOrderListFilter и HolidayOrderListFilter:
Отметим, что у каждого приказа есть одна "Дата" Date, но мы хотим фильтровать приказы по промежутку дат - "Дата с" DateFrom и "Дата по" DateTo, поэтому в фильтре даты две.
Классы AppointmentOrderListFilter и HolidayOrderListFilter будут от него наследоваться:
У приказа о назначении есть Date от базового приказа, а также есть собственные StartDate / EndDate. Поэтому в фильтре будет два промежутка: один для Date из базового класса BaseOrderListFilter, второй для StartDate / EndDate в AppointmentOrderListFilter. Такая же история в HolidayOrderListFilter:
Базовый сервис приказов (продолжение)
Итак, все фильтры для приказов наследуются от BaseOrderListFilter. Теперь мы можем наложить ограничение на TListFilter и продолжить написание BaseOrderService:
Фильтрацию по дате и автору приказа напишите самостоятельно.
Сервис приказов о назначении
Итак, BaseOrderService готов. Теперь напишем AppointmentOrderService. Он будет наследоваться от BaseOrderService:
Метод FilterList() класса BaseOrderService фильтрует только по тем полям, которые есть в базовом фильтре приказов. Но фильтр приказов о назначении имеет дополнительно к этому поля Position ("Должность"), StartDate, EndDate, и надо фильтровать по ним. Эту логику нужно написать в методе FilterList(), который нужно переопределить. Для переопределения метода в Rider нажмите Ctrl + O (от слова "override"). Наберите в нём такой код:
Первым делом мы фильтруем по должности Position. Далее мы фильтруем по промежутку дат: промежуток StartDate - EndDate у приказа должен пересекаться с промежутком StartDate - EndDate фильтра (подчёркнуто жёлтым). Эти неравенства - хитрый логический трюк, который вам стоит запомнить. Кроме того, EndDate у приказа может быть null, и в таком случае приказ должен считаться бессрочным и проходить фильтрацию, поэтому мы проверяем x.EndDate == null (подчёркнуто красным). Между условиями стоит оператор "ИЛИ", потому что мы хотим найти приказы, которые бессрочны ИЛИ попадают в промежуток по дате окончания.
Мы хотим вызвать сначала FilterList() базового сервиса приказов, а затем FilterList() сервиса приказов о назначении. Они дополняют друг друга. Но сейчас каждый из них выполняет всю работу отдельно - берёт список Entities, фильтрует его и превращает в List. Так как каждый из двух методов сам берёт список Entities в качестве отправной точки, то мы не можем сделать так, чтобы сначала фильтровал список один из методов, а потом другой. Поэтому придётся вынести отправную точку за пределы методов FilterList(), в метод GetList(). Эти изменения затронут самый базовый сервис BaseEntityService:
Но теперь FilterList() принимает два аргумента, а не один, как было раньше. Придётся поменять все места использования FilterList() во всех сервисах. Чтобы автоматизировать этот процесс, уберём входной аргумент IList<TEntity> list, написанный вручную, нажмём правой кнопкой на название метода и выберем Refactor - Change Signature... В списке входных параметров добавим нужный:
Нажмём Next. Выберем, как именно модифицировать места вызова метода - автоматически подставить ту переменную, которая имеет нужный тип данных IList<TEntity>:
Откроем, например, PersonService. Нажмём на синюю полоску на полях, чтобы посмотреть изменения. Второй входной аргумент был дописан автоматически и имеет правильный тип данных IList<Person>:
Соответственно, в DisabilitySheetService он имеет тип IList<DisabilitySheet>, а в BaseOrderService - IList<TEntity> (то есть зависит от конкретного сервиса-наследника). Не забудем поправить код внутри FilterList(), чтобы он использовал в качестве изначального списка не Entities, а наш новый list.
Теперь в AppointmentOrderService переопределим GetList(), чтобы он сначала применял фильтр из BaseOrderService, а затем из AppointmentOrderService (чтобы можно было переопределить GetList(), нужно пометить его как virtual):
Всё готово. Пробуйте!
Сервис приказов об отпуске напишите сами.
Далее
Оглавление