[spoiler] В отличие от динамического PHP, большинство языков под .NET CLR 2.0 - языки со статической типизацией. Статическая типизация вкупе с метаданными типов .NET позволяет организовать хорошую поддержку Code Completion (IntelliSense) в среде разработки, проверку ошибок на этапе компиляции и т.д. Чтобы использовать эти преимущества необходим другой подход к сущностям.
Немного истории
Сначала были просто классы и классы-менеджеры, т.е. каждая сущность, например, "Пользователь" представляла собой класс, ее олицетворяющий - BXUser с полями и свойствами, а к нему в нагрузку шел глобальный BXUserManager - у которого были методы по сохранению, обновлению, выборке - все методы содержали прямые вызовы SQL запросов и для каждой новой сущности приходилось писать все с нуля.
Поэтому в недрах отдела зародилась идея сделать единый механизм выполнения типовых запросов к БД, чтобы разработчик тратил меньше время на декларацию сущностей, а пользователь API мог комфортно работать, используя стандартизованные методы и использовать в полной мере IntelliSense в Visual Studio.
Первая версия ORM просуществовала до последнего обновления 3-ей версии, и обкатывалась на инфоблоках. Существенные изменения и дополнения механизм претерпел к версии 4 - теперь это полноценная ORM поверх слоя базы данных.
Что собственно есть
API сущностей решает 2 задачи:
- Отображает слой базы данных на уровень .NET кода
- Автоматизирует решение типовых задач: сохранение сущностей, выборки, фильтрация
есть свойства Author, AuthorBlog, AuthorBlogId, AuthorName, ... , причем свойство Author имеет тип BXBlogUser и тоже в свою очередь имеет разные свойства. Кстати, значение свойства AuthorName автоматически вычисляется при указания идентификатора пользователя через AuthorId
но может быть и задано вручную. Удобно, не правда ли?
Заглядывая внутрь BXBlogComment можно найти метод GetList. Этот могущественный метод - основа всей ORM, который опирается на механизм фильтрации и выборки, но об этом чуть позже. Рассмотрим типовой пример из модуля "Блоги":
BXBlogCommentCollection commentCollection #008000;">= BXBlogComment.#0000FF;">GetList#000000;">( #008000;">new BXFilter#000000;">( #008000;">new BXFilterItem#000000;">(BXBlogComment.#0000FF;">Fields.#0000FF;">Post.#0000FF;">Id, BXSqlFilterOperators.#0000FF;">Equal, #FF0000;">256#000000;">), #008000;">new BXFilterItem#000000;">(BXBlogComment.#0000FF;">Fields.#0000FF;">LiveRootNodeIndex, BXSqlFilterOperators.#0000FF;">Greater, #FF0000;">3#000000;">), #008000;">new BXFilterItem#000000;">(BXBlogComment.#0000FF;">Fields.#0000FF;">LiveRootNodeIndex, BXSqlFilterOperators.#0000FF;">LessOrEqual, #FF0000;">7#000000;">) #000000;">), #008000;">new BXOrderBy#000000;">(#008000;">new BXOrderByPair#000000;">(BXBlogComment.#0000FF;">Fields.#0000FF;">LeftMargin, BXOrderByDirection.#0000FF;">Asc#000000;">)#000000;">), #008000;">new BXSelectAdd#000000;">( BXBlogComment.#0000FF;">Fields.#0000FF;">AuthorBlog.#0000FF;">Id, BXBlogComment.#0000FF;">Fields.#0000FF;">AuthorBlog.#0000FF;">Slug, BXBlogComment.#0000FF;">Fields.#0000FF;">AuthorBlog.#0000FF;">Categories.#0000FF;">Category.#0000FF;">Sites.#0000FF;">SiteId, BXBlogComment.#0000FF;">Fields.#0000FF;">Author, BXBlogComment.#0000FF;">Fields.#0000FF;">Author.#0000FF;">User, BXBlogComment.#0000FF;">Fields.#0000FF;">Author.#0000FF;">User.#0000FF;">Image #000000;">), #0600FF;">null #000000;">)#008000;">;
здесь мы выбираем комментарии для поста в блоге в определенном интервале и сортируем их в нужном порядке.
Первым аргументом 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-кода защищает нас от различных дыр в безопасности.
Как с этим работать
В ходе разработки сайта или модуля разработчикам, возможно, придется столкнуться со следующими задачами:
- Описание сущностей
- Использование сущностей
Класс схемы сущности наследуется от BXScheme и содержит в своем статическом конструкторе (на данный момент это особенность API) описания полей, а также предоставляет доступ к полям через свойства. Например, схема Вашей сущности может выглядеть следующим образом:
#0600FF;">public #FF0000;">class CustomerScheme #008000;">: BXScheme#008000;"><CustomerScheme#008000;">> #000000;">{ #0600FF;">static CustomerScheme#000000;">(#000000;">) #000000;">{ SetTable#000000;">(#666666;">"customer", #666666;">"cm"#000000;">)#008000;">; AddField#000000;">(#666666;">"Id", #008000;">new BXTableField#000000;">(#666666;">"id", SqlDbType.#FF0000;">Int, #FF0000;">0, #0600FF;">false, #0600FF;">true, #0600FF;">true#000000;">)#000000;">)#008000;">; AddField#000000;">(#666666;">"Name", #008000;">new BXTableField#000000;">(#666666;">"name", SqlDbType.#0000FF;">NVarChar, #FF0000;">256, #0600FF;">false, #666666;">""#000000;">)#000000;">)#008000;">; AddField#000000;">(#666666;">"Birthday", #008000;">new BXTableField#000000;">(#666666;">"birthday", SqlDbType.#0000FF;">DateTime, #0600FF;">true, x #008000;">=> DateTime.#0000FF;">Now#000000;">)#000000;">)#008000;">; #000000;">} #0600FF;">public BXSchemeFieldBase Id #000000;">{ get #000000;">{ #0600FF;">return GetField#000000;">(#666666;">"Id"#000000;">)#008000;">; #000000;">} #000000;">} #0600FF;">public BXSchemeFieldBase Name #000000;">{ get #000000;">{ #0600FF;">return GetField#000000;">(#666666;">"Name"#000000;">)#008000;">; #000000;">} #000000;">} #0600FF;">public BXSchemeFieldBase Birthday #000000;">{ get #000000;">{ #0600FF;">return GetField#000000;">(#666666;">"Birthday"#000000;">)#008000;">; #000000;">} #000000;">} #000000;">}
Класс сущности наследуется от BXEntity и содержит в себе описание свойств сущности и пользовательскую бизнес-логику. В самом простом случае это будет выглядеть так:
#0600FF;">public #FF0000;">class Customer #008000;">: BXEntity#008000;"><Customer, CustomerCollection, CustomerScheme#008000;">> #000000;">{ #0600FF;">public #0600FF;">override #FF0000;">string EntityId #000000;">{ get #000000;">{ #0600FF;">return #666666;">"Customer"#008000;">; #000000;">} #000000;">} #0600FF;">public #0600FF;">override #FF0000;">string ModuleId #000000;">{ get #000000;">{ #0600FF;">return #666666;">"MyModule"#008000;">; #000000;">} #000000;">} #0600FF;">public #FF0000;">int Id #000000;">{ get #000000;">{ #0600FF;">return GetValue#008000;"><#FF0000;">int#008000;">>#000000;">(#666666;">"Id"#000000;">)#008000;">; #000000;">} #000000;">} #0600FF;">public #FF0000;">string Name #000000;">{ get #000000;">{ #0600FF;">return GetValue#008000;"><#FF0000;">string#008000;">>#000000;">(#666666;">"Name"#000000;">) #008000;">?? #666666;">""#008000;">; #000000;">} set #000000;">{ SetValue#000000;">(#666666;">"Name", value #008000;">?? #666666;">""#000000;">)#008000;">; #000000;">} #000000;">} #0600FF;">public DateTime Birthday #000000;">{ get #000000;">{ #0600FF;">return GetValue#008000;"><DateTime#008000;">>#000000;">(#666666;">"Birthday"#000000;">)#008000;">; #000000;">} set #000000;">{ SetValue#000000;">(#666666;">"Birthday", value #008000;">!= DateTime.#0000FF;">MinValue #008000;">? #000000;">(#FF0000;">object#000000;">)value #008000;">: #0600FF;">null#000000;">)#008000;">; #000000;">} #000000;">} #000000;">}
Класс коллекции - на данном этапе это просто обертка и описывается одной строкой:
#0600FF;">public #FF0000;">class CustomerCollection #008000;">: BXEntityCollection#008000;"><Customer, CustomerCollection, CustomerScheme#008000;">> #000000;">{#000000;">}
Таким образом для типовых сущностей задача описания носит действительно описательный характер. А с написанием кода может справиться, например, какой-нибудь кодогенератор.
Для более сложной логики класс сущности содержит набор виртуальных и шаблонных методов, которые можно переопределить в зависимости от сложности встраиваемого поведения.
Для критичных операций можно написать методы с прямыми SQL запросами - API сущностей позволяет создать сущность из голой SQL выборки SqlDataReader'ом.
В конечном итоге мы получаем унифицированный механизм для доступа к данным - все сущности имеют метод GetList, набор полей и типизированные свойства. Т.е. нам не придется задумываться, а как выбрать тот или иной объект по такому-то условию - методы выборки одинаковы для всех сущностей, а Visual Studio подскажет, какие поля или свойства есть у сущности.
Заключение
API сущностей - это уже сложившийся механизм, который прошел "боевое крещение" инфоблоками, форумами и блогами, и призван избавить разработчика от необходимости писать рутинные SQL запросы для типовых задач, задействовать особенности статической типизации и использовать удобные средства, которые нам предлагает IDE.
Поддержку LINQ можно реализовать, например, как стороннее решение.
Если честно, то метод BXBlogComment.GetList выглядит ужасно Писать и тем более читать такой код сложновато. А что можно было бы сделать с помощью Entity Framework и LINQ.
Про ASP.NET MVC спрашивать не буду. Разработчикам нравится работать с передовыми технологиями, которые обеспечивают более высокую продуктивность. Надеюсь, что скоро БУС.NET догонит и перегонит мир ASP.NET