Camlex 3.2: реверс-инжиниринг CAML и добавление условий к строковым запросам с помощью лямбда выражений

в 8:45, , рубрики: sharepoint

Некоторое время назад вышел очередной релиз нашего проекта с открытым исходным кодом Camlex.Net. В версию 3.2 была добавлена достаточно интересная функциональность, о которой я хотел бы рассказать в этой статье.

Сначале несколько слов о том, что такое Camlex и как он помогает в разработке. Если вы работаете с Sharepoint, то скорее всего сталкивались с необходимостью писать CAML запросы. CAML (Collaborative Application Markup Language) — специальный язык, используемый в Sharepoint для различных целей. Одно из его применений — написание Sql-подобных запросов для выборки данных из листов и библиотек документов. Например, для того, чтобы выбрать все элементы из списка, в поле Title которых указано значение «Meeting», а в Description — «Sharepoint», нужно использовать следующий запрос:

<Where>
  <And>
    <Eq>
      <FieldRef Name="Title" />
      <Value Type="Text">Meeting</Value>
    </Eq>
    <Eq>
      <FieldRef Name="Description" />
      <Value Type="Text">Sharepoint</Value>
    </Eq>
  </And>
</Where>

Если вам понадобится добавить больше условий к запросу, вам придется перестраивать все Xml-дерево (в CAML элементы And и Or могут иметь только 2 операнда). Кроме того, если вы работаете со строками, компилятор не помогает вам, отлавливая ошибки на этапе компиляции.

С помощью Camlex вы можете написать этот запрос, используя лямбда выражения на C#:

string query = Camlex.Query()
    .Where(x => (string)x["Title"] == "Meeting" && (string)x["Description"] == "Sharepoint").ToString();

Кроме этого в Camlex-е много полезных вещей для прикладных разработчиков. Больше деталей можно посмотреть в другой статье на Хабре, которая была опубликована около 2-х лет назад, когда мы выпустили первую версию проекта (мы — это я и Владимир Тимашков): Использование лямбд для построения CAML-запросов в SharePoint'е, а также в моем блоге статьи с меткой Camlex.Net: http://sadomovalex.blogspot.fi/search/label/Camlex.NET.

Теперь непосредственно к теме поста. Около полугода назад мы добавили достаточно нетривиальную функциональность в проект: реверс-инжиниринг CAML-а. Она позволяет разбирать строковые запросы и содавать на их основе дерево выражений (expression tree). С помощью реверс-инжиниринга мы запустили бесплатный онлайн сервис http://camlex-online.org, с помощью которого можно конвертировать строковые CAML-запросы в C# код. Т.е. получился Camlex наоборот. Если интересны детали, генерация C# кода из дерева выражений сделана с помощью другого проекта с открытым кодом ExpressionToCode (правда немного допиленного нами).

Недавно мы получили несколько запросов: разработчики спрашивали, можно ли добавлять дополнительные условия к существующим CAML-запросам? Задача показалась мне интересной и с учетом того, что у нас уже есть реверс-инжиниринг, вполне выполнимой. В версию 3.2 была добавлена эта функциональность. Теперь можно использовать Camlex вместе с инструментами, которые работают с обычными строковыми запросами, расширять их с помощью Camlex-а и передавать результат назад в виде строки.

Давайте посмотрим пример. Представим, что у нас есть следующий запрос:

<Where>
  <Eq>
    <FieldRef Name="Title" />
    <Value Type="Text">Sharepoint</Value>
  </Eq>
</Where>

он вернет нам элементы с заголовком «Sharepoint». Допустим, что нам надо добавить еще одно условие к запросу, чтобы также получить элементы с заголовком «Office». Теперь это можно сделать так:

string query = Camlex.Query().WhereAny(existingQuery,  x => (string)x["Title"] == "Office").ToString();

т.е. просто передаем строковый запрос и новое условие в виде лямбда выражения. Результат будет следующим:

<Where>
  <Or>
    <Eq>
      <FieldRef Name="Title" />
      <Value Type="Text">Sharepoint</Value>
    </Eq>
    <Eq>
      <FieldRef Name="Title" />
      <Value Type="Text">Office</Value>
    </Eq>
  </Or>
</Where>

Можно также добавлять сразу несколько условий к более сложным строковым запросам. Вот список методов, которыми был расширен интерфейс IQuery:

public interface IQuery
{
    // ...
    IQuery WhereAll(string existingWhere, Expression<Func<SPListItem, bool>> expression);
    IQuery WhereAll(string existingWhere, IEnumerable<Expression<Func<SPListItem, bool>>> expressions);
    IQuery WhereAny(string existingWhere, Expression<Func<SPListItem, bool>> expression);
    IQuery WhereAny(string existingWhere, IEnumerable<Expression<Func<SPListItem, bool>>> expressions);
    IQuery OrderBy(string existingOrderBy, Expression<Func<SPListItem, object>> expr);
    IQuery OrderBy(string existingOrderBy, Expression<Func<SPListItem, object[]>> expr);
    IQuery OrderBy(string existingOrderBy, IEnumerable<Expression<Func<SPListItem, object>>> expressions);
    IQuery GroupBy(string existingGroupBy, Expression<Func<SPListItem, object>> expr);
    IQuery GroupBy(string existingGroupBy, Expression<Func<SPListItem, object[]>> expr);
}

Т.е. можно расширять не только условия запросов (WhereAll и WhereAny), но и OrderBy и GroupBy. Также можно расширять ViewFields, но эти методы находятся в другом интерфейсе, т.к. не являются частью общей цепочки вызовов при конструировании запроса.

Рассмотрим более сложный пример. Добавим к строковому запросу, который мы получили выше, сразу два условия: выбрать также элементы, в которых число участников больше 1 и статус не пустой:

string existingQuery =
    "<Where>" +
    "  <And>" +
    "    <Eq>" +
    "      <FieldRef Name="Title" />" +
    "      <Value Type="Text">Sharepoint</Value>" +
    "    </Eq>" +
    "    <Eq>" +
    "      <FieldRef Name="Title" />" +
    "      <Value Type="Text">Office</Value>" +
    "    </Eq>" +
    "  </And>" +
    "</Where>";

var query = Camlex.Query().WhereAny(existingQuery,
    x => (int)x["Participants"] > 1 && x["Status"] != null).ToString();

В результате получим:

<Where>
  <Or>
    <And>
      <Gt>
        <FieldRef Name="Participants" />
        <Value Type="Integer">1</Value>
      </Gt>
      <IsNotNull>
        <FieldRef Name="Status" />
      </IsNotNull>
    </And>
    <And>
      <Eq>
        <FieldRef Name="Title" />
        <Value Type="Text">Sharepoint</Value>
      </Eq>
      <Eq>
        <FieldRef Name="Title" />
        <Value Type="Text">Office</Value>
      </Eq>
    </And>
  </Or>
</Where>

Т.е. Camlex берет всю работу по формированию достаточно сложного CAML-запроса на себя.

В заключение хотелось сказать, что Camlex — это проект, который развивается из запросов сообщества. Если у вас есть идеи и предложения по добавлению новой функциональности, пишите их на сайте проекте в разделе Обсуждения. Стараемся отвечать на все вопросы и по мере возможностей добавлять новую функциональность.

Автор: sadomovalex

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js