Поддержка операции IN в Camlex 3.5 и Camlex.Client 1.3

Этим летом я выпустил новые версии проектов с открытым исходным кодом: Camlex и Camlex.Client (на момент написания этой статьи, был выпущен еще один релиз для клиентской версии Camlex-а 1.4, однако здесь речь пойдет об изменениях, добавленных в версии 1.3. Версия основного Camlex-а не изменилась, т.е. мы будем говорить о Camlex 3.5). Сначала я покажу несколько примеров использования, а затем скажу несколько слов об использовании Camlex в разных версиях Sharepoint.

Что такое операция IN? Вот что говорит msdn по этому поводу:

Определяет, равно ли значение элемента списка для поля, заданное элементом FieldRef, одному из значений, заданных элементом Values.

Например, если нам надо получить список элементов, у которых целочисленное поле содержит значение в интервале [0..9], то мы можем использовать следующий запрос:

<br>&lt;Where&gt;<br>&lt;In&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Values&gt;<br>&lt;Value Type="Integer"&gt;0&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;1&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;2&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;3&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;4&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;5&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;6&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;7&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;8&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;9&lt;/Value&gt;<br>&lt;/Values&gt;<br>&lt;/In&gt;<br>&lt;/Where&gt;<br>

В предыдущих версиях Camlex для получения такого же результата можно было скомбинировать несколько Eq (==) условий с помощью операции Or. Camlex специально проектировался для выполнения таких задач, для упрощения создания динамических CAML запросов, так что решить задачу можно очень просто одной строкой:

<br>string caml = Camlex.Query().WhereAny(<br>Enumerable.Range(0, 9).Select&lt;int, Expression&lt;Func&lt;SPListItem, bool&gt;&gt;&gt;(<br>i =&gt; x =&gt; (int) x["Count"] == i)).ToString();<br>

Давайте посмотрим на результат выполнения этого кода:

<br>&lt;Where&gt;<br>&lt;Or&gt;<br>&lt;Or&gt;<br>&lt;Or&gt;<br>&lt;Or&gt;<br>&lt;Or&gt;<br>&lt;Or&gt;<br>&lt;Or&gt;<br>&lt;Or&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;0&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;1&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;/Or&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;2&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;/Or&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;3&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;/Or&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;4&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;/Or&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;5&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;/Or&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;6&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;/Or&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;7&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;/Or&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;8&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;/Or&gt;<br>&lt;/Where&gt;<br>

Сравнивая с первым примером, выглядит не очень красиво. Интервал значения может быть и больше, в этом случае запрос будет еще более развернутым. Однако теперь, с добавлением поддержки операции IN, можно написать запрос, используя следующий синтаксис:

<br>string caml =<br>Camlex.Query().Where(x =&gt; Enumerable.Range(0, 9).Contains((int)x["Count"]))<br>.ToString();<br>

Он создаст CAML запрос, который мы уже видели:

<br>&lt;Where&gt;<br>&lt;In&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Values&gt;<br>&lt;Value Type="Integer"&gt;0&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;1&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;2&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;3&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;4&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;5&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;6&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;7&lt;/Value&gt;<br>&lt;Value Type="Integer"&gt;8&lt;/Value&gt;<br>&lt;/Values&gt;<br>&lt;/In&gt;<br>&lt;/Where&gt;<br>

Для тех, кто работал с NHibernate или Linq 2 Sql, такой синтаксис не будет новым: эти фреймворки используют похожий прием для генерации Sql запросов с операцией IN. Я специально использовал пример с динамически наполняемым массивом значений (Enumerable.Range(0, 9)), т.к. хотел показать, что код будет работать с любым выражением или функцией, возвращающей IEnumerable:

<br>var caml = Camlex.Query().Where(x =&gt; getArray().Contains((int)x["Count"])).ToString();<br>...<br>List&lt;int&gt; getArray()<br>{<br>var list = new List&lt;int&gt;();<br>for (int i = 0; i &lt; 10; i++)<br>{<br>list.Add(i);<br>}<br>return list;<br>}<br>

(Вы видите разницу между последним примером, в котором функция возвращает List<int>, и предыдущим примером, который использует IEnumerable? Если да, то вы хорошо знаете C# 🙂 Класс List<T> имеет свой Contains метод, тогда как для IEnumerable<int> из первого примера, используется метод расширения. Как видно, Camlex может работать с обоими. Если вы используете Linq, не забудьте добавить “using System.Linq;” в начало вашего cs файла).

Т.е. основной синтаксис для новой функциональности следующий:

<br>var caml = Camlex.Query().Where(x =&gt;<br>enumerable.Contains((Type)x["FieldTitle"])).ToString();<br>

Конечно, также вы можете использовать массив с константами:

<br>string c = Camlex.Query().Where(x =&gt; new[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}<br>.Contains((int) x["Count"])).ToString();<br>

Также он будет работать с любыми типами для элемента Value:

<br>string c = Camlex.Query().Where(x =&gt; new[]<br>{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"}<br>.Contains((string) x["Title"])).ToString();<br>

Этот пример создаст следующий CAML:

<br>&lt;Where&gt;<br>&lt;In&gt;<br>&lt;FieldRef Name="Title" /&gt;<br>&lt;Values&gt;<br>&lt;Value Type="Text"&gt;zero&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;one&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;two&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;three&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;four&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;five&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;six&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;seven&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;eight&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;nine&lt;/Value&gt;<br>&lt;/Values&gt;<br>&lt;/In&gt;<br>&lt;/Where&gt;<br>

Как видите, теперь он использует Text тип в элементах Value.

Это была только половина истории. Если вы следите за Camlex-ом, то возможно помните, что начиная с версии 3.0 он стал двунаправленным (см. здесь: Camlex.NET became bidirectional and goes online. Version 3.0 is released). Так что преобразования были добавлены также в обе стороны: из дерева выражений (expression tree) в CAML и из CAML в дерево выражений. Т.е. можно взять CAML запрос в строковой переменной, и получить дерево выражений из него:

<br>var xml =<br>"&lt;Query&gt;" +<br>" &lt;Where&gt;" +<br>" &lt;In&gt;" +<br>" &lt;FieldRef Name=\"Title\" /&gt;" +<br>" &lt;Values&gt;" +<br>" &lt;Value Type=\"Text\"&gt;zero&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;one&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;two&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;three&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;four&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;five&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;six&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;seven&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;eight&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;nine&lt;/Value&gt;" +<br>" &lt;/Values&gt;" +<br>" &lt;/In&gt;" +<br>" &lt;/Where&gt;" +<br>"&lt;/Query&gt;";<br><br>var expr = Camlex.QueryFromString(xml).ToExpression();<br>

Эта возможность дает много интересных приложений:

1. на http://camlex-online.org/ добавьте xml, показанный выше в поле ввода и нажмите Convert to C#. Он покажет вам, как такой запрос можно написать на Camlex-е:

<br>Camlex.Query().Where(x =&gt; new[] {<br>"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" }<br>.Contains((string)x["Title"]))<br>

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

<br>var xml =<br>"&lt;Query&gt;" +<br>" &lt;Where&gt;" +<br>" &lt;In&gt;" +<br>" &lt;FieldRef Name=\"Title\" /&gt;" +<br>" &lt;Values&gt;" +<br>" &lt;Value Type=\"Text\"&gt;one&lt;/Value&gt;" +<br>" &lt;Value Type=\"Text\"&gt;two&lt;/Value&gt;" +<br>" &lt;/Values&gt;" +<br>" &lt;/In&gt;" +<br>" &lt;/Where&gt;" +<br>"&lt;/Query&gt;";<br><br>string caml = Camlex.Query().WhereAll(xml, x =&gt; (int)x["Count"] == 1).ToString();<br>

произведет следующий CAML:

<br>&lt;Where&gt;<br>&lt;And&gt;<br>&lt;Eq&gt;<br>&lt;FieldRef Name="Count" /&gt;<br>&lt;Value Type="Integer"&gt;1&lt;/Value&gt;<br>&lt;/Eq&gt;<br>&lt;In&gt;<br>&lt;FieldRef Name="Title" /&gt;<br>&lt;Values&gt;<br>&lt;Value Type="Text"&gt;one&lt;/Value&gt;<br>&lt;Value Type="Text"&gt;two&lt;/Value&gt;<br>&lt;/Values&gt;<br>&lt;/In&gt;<br>&lt;/And&gt;<br>&lt;/Where&gt;<br>

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

Это была часть с примерами. Теперь несколько слов про использования Camlex в различных версиях Sharepoint. Изначально Camlex был создан для Sharepoint 2007 и он использует ссылку на Microsoft.SharePoint.dll версии 12.0.0.0 (он использует ссылку только для компиляции. Camlex не вызывает никаких методов Sharepoint API). Благодаря автоматическому перенаправлению биндинга сборок в Sharepoint (assembly binding redirect, про который я писал здесь: Assembly binding redirect in Sharepoint 2010: how old code for SP 2007 works in SP 2010) он также работает в версиях 2010 и 2013. Операция IN была добавлена в схему CAML в Sharepoint 2010, т.е. запросы с ней не будут работать в Sharepoint 2007. Так что перед релизом версии 3.5 мне нужно было сделать выбор между несколькими путями дальнейшего развития Camlex:

1. добавить новую функциональность в существующую ветку Camlex и продолжать использовать Microsoft.SharePoint.dll версии 12.0.0.0. Преимущество: одна сборка работает со всеми версиями Sharepoint, что проще для разработчиков. Недостаток: элемент IN можно будет также создать в Sharepoint 2007, но по факту такой запрос не будет работать.

2. создать отдельную ветку для Sharepoin 2007 и оставить ссылку на версию 12.0.0.0 только в ней, в то время, как в основной ветке изменить ее на 14.0.0.0, так что ее можно будет использовать только начиная с Sharepoint 2010, где операция IN поддерживается. Преимущества и недостатки подхода, такие же как в 1-м подходе, только в обратном направлении.

Также во втором подходе было бы необходимо поддерживать еще одну ветку (сейчас я поддерживаю 2 ветки: default для основного Camlex и client для клиентской версии. Операция IN добавлена в Camlex.Client в версии 1.3). Так как на текущий момент я единственный активный contributor проекта и время (хотя все чаще приходят pull request-ы, за что большое спасибо сообществу), которое я могу тратить на проект, очень ограничено, я выбрал первый путь (по крайней мере пока). В будущем я не исключаю возможности создать различные ветки, но пока будет одна ветка, из которой будет собираться Camlex для всех версий Sharepoint-а.

С обновлением сборок на Codeplex-е, я также обновил пакеты nuget. Вы можете добавить последнюю версию с помощью следующих команд:

Install-Package Camlex.NET.dll

и для клиентской версии:

Install-Package Camlex.Client.dll

В следующих релизах я планирую добавить оставшиеся новые элементы, добавленные в схему CAML с Sharepoint 2010 (Include, NotInclude) и Join-ы (которые тоже конечно надо добавить наконец).

Реклама

Об авторе sadomovalex

Старший инженер, team lead, консультант. Работаю в стеке .Net. Последние несколько лет занимаюсь разработкой enterprise приложений под Sharepoint, чему и будет в основном посвящена тематика этого блога. Также активно использую и интересуюсь ASP.Net MVC, DDD, TDD, Agile. Активно участвую в жизни многих профессиональных сообществ, SPb .Net UG, SPb ALT.Net, rsdn, Finland SP UG и др.
Запись опубликована в рубрике CAML, Camlex.NET, Sharepoint. Добавьте в закладки постоянную ссылку.

11 комментариев на «Поддержка операции IN в Camlex 3.5 и Camlex.Client 1.3»

  1. Как работать с lookup полями использую IN ?

  2. Сергей,
    у вас multiple lookup или single lookup? Примеры (напр. http://rmanimaran.wordpress.com/2011/03/11/new-in-sharepoint-2010-caml-query/ или http://social.msdn.microsoft.com/Forums/sharepoint/en-US/fbece329-3c84-4bd6-9615-2686a0ff3efe/caml-query-multilookup-field-from-splist) говорят о том, что будет работать простой Eq (==). Т.к. In по идее просто более простая конструкция для нескольких Eq, соединенных Or-ом, то возможно будет работать и с lookup-ами. С другой стороны, в Sharepoint единственная возможность сказать что-то наверняка, это попробовать самому.

    • Сергей:

      У меня мульти лукап, в нем собственно-то и происходит не пойми что, сложно даже описать не предсказуемость поведения, думал у вас есть вариант решения, который не выдает мистику на выходе такой query.

      • Допустим у вас есть поле MyLookup, в котором могут храниться несколько значений. В примерах выше показано, что простой Eq будет работать. Т.е. запрос Camlex.Query().Where(x => (string)x[«MyLookup»] == «foo» || (string)x[«MyLookup»] == «bar») должен вернуть все записи, которые содержат помимо других значений foo или bar, напр. записи, у которых в MyLookup записано «foo;test» или «example;bar». Попробуйте, будет ли это работать. Если будет, то и In не нужен. Также эту конструкцию можно создать динамически с помощью WhereAny: http://www.gotdotnet.ru/blogs/sadomovalex/8155/.

      • Спасибо за комментарий, без In уже сделано и работает, было желание сократить длину запроса и избавится от множества оперраторов Eq и Or

  3. dvd73:

    Не нашел возможности работы с Lookup полями по id. Т.е. сделать так чтобы в выходной строке появилась опция Lookupid=»true»

    • dvd73,
      пример можно найти тут: http://sadomovalex.blogspot.com/2010/04/camlexnet-20-for-sharepoint-released.html. Используйте следующий синтаксис:
      string query = Camlex.Query().Where(x => x[«Status»] == (DataTypes.LookupId)»1″).ToString();

      • dvd73:

        Это пример того как использовать lookup поля в Camlex. А есть пример как использовать в Camlex lookup поля в конструкции IN если необходимо построить выражение по Id? Возьмем пример который у вас здесь дан и немного исправим:

        1
        2
        3

        С точки зрения CAML это правильный запрос, но как его построить в Camlex?

  4. dvd73:

    Это пример того как использовать lookup поля в Camlex. А есть пример как использовать в Camlex lookup поля в конструкции IN если необходимо построить выражение по Id? Возьмем пример который у вас здесь дан и немного исправим:

    1
    2
    3

    С точки зрения CAML это правильный запрос, но как его построить в Camlex?

    • wordpress режет html и xml разметку в комментариях, но я понял, о чем вы говорите. Такой функциональности пока нет. Самый простой workaround это подправить полученную строку:
      string query = Camlex.Query().Where(…);
      query = query.Replace(«Name=\»Title\»», «Name=\»Title\» LookupId=\»True\»»);
      Добавил эту фичу в further development list на codeplex-е.

      • dvd73:

        спасибо, вообщем то я так и сделал. А именно:
        1. Воспользовался Camlex для построения динамического выражения с использованием IN, при этом lookup поля получились без аттрибута Lookupid=»true»
        2. Затем добавил аттрибут Lookupid=»true» в полученную строку запроса, с помощью XElement

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s