ORM-система в «Битрикс»

Сегодня я расскажу про такую составляющую системы «1С-Битрикс: Управление сайтом ASP.NET», как сущности. Этот механизм — один из основополагающих механизмов системы, претерпел значительные изменения к 4-й версии.

В отличие от динамического PHP, большинство языков под .NET CLR 2.0 — языки со статической типизацией. Статическая типизация вкупе с метаданными типов .NET позволяет организовать хорошую поддержку Code Completion (IntelliSense) в среде разработки, проверку ошибок на этапе компиляции и т.д. Чтобы использовать эти преимущества необходим другой подход к сущностям.

Немного истории

Сначала были просто классы и классы-менеджеры, т.е. каждая сущность, например, «Пользователь» представляла собой класс, ее олицетворяющий — BXUser с полями и свойствами, а к нему в нагрузку шел глобальный BXUserManager — у которого были методы по сохранению, обновлению, выборке — все методы содержали прямые вызовы SQL запросов и для каждой новой сущности приходилось писать все с нуля.

Поэтому в недрах отдела зародилась идея сделать единый механизм выполнения типовых запросов к БД, чтобы разработчик тратил меньше время на декларацию сущностей, а пользователь API мог комфортно работать, используя стандартизованные методы и использовать в полной мере IntelliSense в Visual Studio.

Первая версия ORM просуществовала до последнего обновления 3-ей версии, и обкатывалась на инфоблоках. Существенные изменения и дополнения механизм претерпел к версии 4 — теперь это полноценная ORM поверх слоя базы данных.

Что собственно есть

API сущностей решает 2 задачи:

* Отображает слой базы данных на уровень .NET кода
* Автоматизирует решение типовых задач: сохранение сущностей, выборки, фильтрация

Таким образом, для каждой записи в БД мы имеем .NET обертку, со свойствами и методами, а ля ActiveRecord. Свойства и методы могут скрывать различные аспекты бизнес-логики, будь то автоматический расчет дополнительных значений, верификация пользовательского ввода, запуск различных механизмов. Например, сущность «комментарий блога» представлена классом BXBlogComment, у которого, как подсказывает IntelliSense :)

image

есть свойства Author, AuthorBlog, AuthorBlogId, AuthorName,…, причем свойство Author имеет тип BXBlogUser и тоже в свою очередь имеет разные свойства. Кстати, значение свойства AuthorName автоматически вычисляется при указания идентификатора пользователя через AuthorId

image

но может быть и задано вручную. Удобно, не правда ли?

Заглядывая внутрь BXBlogComment можно найти метод GetList. Этот могущественный метод — основа всей ORM, который опирается на механизм фильтрации и выборки, но об этом чуть позже. Рассмотрим типовой пример из модуля «Блоги»:

  1. BXBlogCommentCollection commentCollection = BXBlogComment.GetList(
  2. new BXFilter(
  3. new BXFilterItem(BXBlogComment.Fields.Post.Id, BXSqlFilterOperators.Equal, 256),
  4. new BXFilterItem(BXBlogComment.Fields.LiveRootNodeIndex, BXSqlFilterOperators.Greater, 3),
  5. new BXFilterItem(BXBlogComment.Fields.LiveRootNodeIndex, BXSqlFilterOperators.LessOrEqual, 7)
  6. ),
  7. new BXOrderBy(new BXOrderByPair(BXBlogComment.Fields.LeftMargin, BXOrderByDirection.Asc)),
  8. new BXSelectAdd(
  9. BXBlogComment.Fields.AuthorBlog.Id,
  10. BXBlogComment.Fields.AuthorBlog.Slug,
  11. BXBlogComment.Fields.AuthorBlog.Categories.Category.Sites.SiteId,
  12. BXBlogComment.Fields.Author,
  13. BXBlogComment.Fields.Author.User,
  14. BXBlogComment.Fields.Author.User.Image
  15. ),
  16. null
  17. );
* This source code was highlighted with Source Code Highlighter.

 

здесь мы выбираем комментарии для поста в блоге в определенном интервале и сортируем их в нужном порядке.

 

Первым аргументом GetList'а идет фильтр. Фильтры бывают разными. Атомарным фильтром является является фильтр «поле равно значение» — он олицетворен классом BXFilterItem. Еще есть фильтр AND (BXFilter) и OR (BXFilterOr). В этом примере мы выбираем те комментарии, у которых LiveRootNodeIndex лежит в диапазоне от 4 до 7, а идентификатор связанной сущности BXBlogPost равен 256.

 

Второй аргумент — это порядок сортировки — тут все просто, задаем парами вида поле-направление.

 

Третий аргумент — еще одна мощная вещь — это выборка. При помощи BXSelect можно указать список полей, которые нужно выбрать в запросе, а также полей связанных сущностей (API автоматически сделает нужные JOIN'ы). Таким образом мы, например, для каждого комментария можем выбрать дополнительно нужную информацию о его авторе и о блоге автора. Все это будет сделано в рамках одного запроса. Конечно, мы можем не указывать дополнительных сущностей в выборке — к ним все равно можно будет обратиться через свойства комментария, но уже с дополнительным запросом по требованию. В нашем случае мы дополнительно выбираем (BXSelectAdd означает, что перечисленные поля и сущности будут выбраны дополнительно к основной) 2 поля из связанной сущности AuthorBlog (блог автора) — идентификатор и адрес, и список идентификаторов сайтов, к которым принадлежат категории, к которым принадлежит все тот же блог автора комментария. Т.е. API позволяет нам сделать глубокую выборку связанных данных. Даже множественных. Ну и в добавок нам понадобится информация об авторе комментария и его аватар — это уже конкретные сущности (BXBlogUser, BXUser, BXFile) — они будут выбраны полностью, со всеми полями.

 

Обратите внимание, что поля сущности задаются не строками, а свойствами объекта BXBlogComment.Fields — схемы сущности. Схема сущности описывается отдельным классом и содержит поля сущности и их привязку к базе данных. Поля могут описывать простые свойства, имеющие прямые прототипы в базе данных (BXTableField), выражения, чья логика отображения задается кодом (BXCalculatedField), одиночные и множественные связки с другими сущностями (BXSchemeField, BXLinkedField).

 

Таким образом общение с сущностями происходит на уровне .NET кода, а не SQL — тем самым минимизируется вероятность ошибки, ведь корректность синтаксиса теперь за нас проверяет компилятор, а отсутствие прямых вставок SQL-кода защищает нас от различных дыр в безопасности.



Как с этим работать

В ходе разработки сайта или модуля разработчикам, возможно, придется столкнуться со следующими задачами:

 

  • Описание сущностей
  • Использование сущностей

 

Описание сущности сводится к объявлению 3-х классов: схемы сущности, собственно сущности и класса коллекции

 

Класс схемы сущности наследуется от BXScheme и содержит в своем статическом конструкторе (на данный момент это особенность API) описания полей, а также предоставляет доступ к полям через свойства. Например, схема Вашей сущности может выглядеть следующим образом:

 

  1. public class CustomerScheme : BXScheme<CustomerScheme>
  2. {
  3. static CustomerScheme()
  4. {
  5. SetTable("customer", "cm");
  6. AddField("Id", new BXTableField("id", SqlDbType.Int, 0, false, true, true));
  7. AddField("Name", new BXTableField("name", SqlDbType.NVarChar, 256, false, ""));
  8. AddField("Birthday", new BXTableField("birthday", SqlDbType.DateTime, true, x => DateTime.Now));
  9. }
  10. public BXSchemeFieldBase Id { get { return GetField("Id"); } }
  11. public BXSchemeFieldBase Name { get { return GetField("Name"); } }
  12. public BXSchemeFieldBase Birthday { get { return GetField("Birthday"); } }
  13. }
* This source code was highlighted with Source Code Highlighter.

 

Класс сущности наследуется от BXEntity и содержит в себе описание свойств сущности и пользовательскую бизнес-логику. В самом простом случае это будет выглядеть так:

 

  1. public class Customer : BXEntity<Customer, CustomerCollection, CustomerScheme>
  2. {
  3. public override string EntityId { get { return "Customer"; } }
  4. public override string ModuleId { get { return "MyModule"; } }
  5. public int Id
  6. {
  7. get { return GetValue<int>("Id"); }
  8. }
  9. public string Name
  10. {
  11. get { return GetValue<string>("Name") ?? ""; }
  12. set { SetValue("Name", value ?? ""); }
  13. }
  14. public DateTime Birthday
  15. {
  16. get { return GetValue<DateTime>("Birthday"); }
  17. set { SetValue("Birthday", value != DateTime.MinValue ? (object)value : null); }
  18. }
  19. }
* This source code was highlighted with Source Code Highlighter.

 

Класс коллекции — на данном этапе это просто обертка и описывается одной строкой:

 

  1. public class CustomerCollection : BXEntityCollection<Customer, CustomerCollection, CustomerScheme> {}
* This source code was highlighted with Source Code Highlighter.

 

Таким образом для типовых сущностей задача описания носит действительно описательный характер. А с написанием кода может справиться, например, какой-нибудь кодогенератор. :)

 

Для более сложной логики класс сущности содержит набор виртуальных и шаблонных методов, которые можно переопределить в зависимости от сложности встраиваемого поведения.

 

Для критичных операций можно написать методы с прямыми SQL запросами — API сущностей позволяет создать сущность из голой SQL выборки SqlDataReader'ом.

 

В конечном итоге мы получаем унифицированный механизм для доступа к данным — все сущности имеют метод GetList, набор полей и типизированные свойства. Т.е. нам не придется задумываться, а как выбрать тот или иной объект по такому-то условию — методы выборки одинаковы для всех сущностей, а Visual Studio подскажет, какие поля или свойства есть у сущности.



Заключение

API сущностей — это уже сложившийся механизм, который прошел «боевое крещение» инфоблоками, форумами и блогами, и призван избавить разработчика от необходимости писать рутинные SQL запросы для типовых задач, задействовать особенности статической типизации и использовать удобные средства, которые нам предлагает IDE.