Приложение в твоем смартфоне
Приложение в твоем смартфоне
Приложение в твоем смартфоне
Приложение в твоем смартфоне
1 февраля 2010 в 23:19

Использование лямбд для построения CAML-запросов в SharePoint'е

Статья от коллег, SharePoint-разработчиков.
Это статья написана для тех разработчиков SharePoint-based продуктов, которые время от времени сталкиваются с необходимостью делать выборки из одного и/или нескольких списков SharePoint'а — что, в принципе, случается практически всегда :-). В этой статье я рассмотрю несколько способов реализаций таких выборок с их достоинствами и недостатками. В конце статьи, я дам пример использования недавно опубликованного проекта Camlex.NET (http://camlex.codeplex.com/), который кажется мне наиболее удобным для этого случая. Итак…

Первый подход


Первый, он же самый простой, способ — пробежаться по списку, отобрать нужные записи и, возможно, произвести некоторые операции с ними, например, отсортировать:

  1.     SPWeb web = null;
  2.     SPList list = web.Lists["SomeList"];
  3.  
  4.     List foundItems = new List();
  5.     for(int i=0; i<list.Items.Count; i++)
  6.     {
  7.         SPListItem item = list.Items[i];
  8.         if ((string) item["TextField"] == "Test" &&
  9.             ((int) item["IntField"] != 1 || (DateTime)item["DateField"] < DateTime.Today))
  10.         {
  11.             foundItems.Add(item);
  12.         }
  13.     }
  14.     foundItems.Sort(
  15.         (x, y) => ((int)x["SortField"]).CompareTo((int)y["SortField"]));
* This source code was highlighted with Source Code Highlighter.


Хотя пример достаточно тяжелый, но в нем представлены практически все широко используемые вещи — поля разных типок (string, int, DateTime), разные операции сравнения (==, !=, <), разные логические операции (AND, OR) и, в довесок, есть еще и сортировка. Семантика фильтра/сортировки в нашем случае не важна, так что будем обращать внимание только на синтаксис.

Итак — рассмотрим этот пример. Он в old-school стиле — простой и понятный. Однако же производительность такого кода удовлетворительна только на очень небольших списках, содержащих буквально несколько записей. На списках побольше (положим, 1,000 записей) — такой код жутко тормозит. Проблема в том, что каждая запись вытягивается из списка (а это значит — из content database) отдельным SQL запросом, в то время как фильтрующая/сортирующая логика располагается в C# коде.

Для решения этой проблемы с производительностью, комания Microsoft разработала классы SPQuery и SPSiteDataQuery. Эти классы принимают SQL-like запросы, отформатированные в XML, и, с их помощью, можно получить уже окончательный результат — соответственно, фильтры и сортировка/группировка исполняются внутри SQL движка, а не в C# коде. Итак:

Второй подход


Этот подход оптимален по производительности. Следующий пример делает все тоже самое, что и в первом примере, но использует уже SPQuery класс:

  1.     SPQuery query = new SPQuery();
  2.     query.Query =
  3.         "<Where>" +
  4.         "    <And>" +
  5.         "        <Eq>" +
  6.         "            <FieldRef Name=\"TextField\" />" +
  7.         "            <Value Type=\"Text\">Test</Value>" +
  8.         "        </Eq>" +
  9.         "        <Or>" +
  10.         "            <Neq>" +
  11.         "                <FieldRef Name=\"IntField\" />" +
  12.         "                <Value Type=\"Integer\">1</Value>" +
  13.         "            </Neq>" +
  14.         "            <Lt>" +
  15.         "                <FieldRef Name=\"DateField\" />" +
  16.         "                <Value Type=\"DateTime\">" +
  17.         "                    <Today />" +
  18.         "                </Value>" +
  19.         "            </Lt>" +
  20.         "        </Or>" +
  21.         "    </And>" +
  22.         "</Where>" +
  23.         "<OrderBy>" +
  24.         "    <FieldRef Name=\"SortField\" />" +
  25.         "</OrderBy>";
  26.         SPListItemCollection resultItems = list.GetItems(query);
* This source code was highlighted with Source Code Highlighter.


Все работает и работает быстро. Отлично!
Но и проблемы тоже налицо:
  • Любая синтаксическая ошибка при форматировании XML вызывает exception.
  • Тотально увеличивается нечитабельность кода — мало того, что размер примера вырос в 2 раза, читать его становится существенно тяжелее по сравнению с первым подходом.
  • При изменении фильтра надо перестроить дерево XML узлов — к примеру, узел каждой логической операции принимает толька 2 операнда, и в случае, если вам надо добавить еще одно условие в OR, то Вам придется разбивать соответствующий подузел на 2 части, и, в нашем случае, общая глубина дерева становиться уже пугающей.
  • Переход с привычного C# кода на вариант представления кода в виде XML — и, как следствие, потеря compile-time проверок, Intellisense, и т.д. и т.п. С учетом глобальной тенденции к поддержке code-first approach'а, этот недостаток становиться просто критичным.
Следствие — общее недовольство разработчика :-)

Третий подход


В следующем примере мы будем использовать open-source проект CAML.NETcamldotnet.codeplex.com/ — (мне тут правда пришлось чуть-чуть помучаться, так как примеры из документации к проекту не работают — в API используется отсутствующий CAML.Query() метод).
Опять таки, следующий пример выполняет ровно все тоже, что и предыдущие примеры (расстановку скобочек я оставил аутентичной — так же, как используется в примерах к проекту):

  1.     SPQuery query2 = new SPQuery();
  2.     query2.Query =
  3.         CAML.Where(
  4.             CAML.And(
  5.                 CAML.Eq(
  6.                     CAML.FieldRef("TextField"),
  7.                     CAML.Value("Test")),
  8.                 CAML.Or(
  9.                     CAML.Neq(
  10.                         CAML.FieldRef("IntField"),
  11.                         CAML.Value(1)),
  12.                     CAML.Lt(
  13.                         CAML.FieldRef("DateField"),
  14.                         CAML.Value(DateTime.Today))))) +
  15.         CAML.OrderBy(
  16.             CAML.FieldRef("SortField"));
  17.     SPListItemCollection resultItems2 = list.GetItems(query2);
* This source code was highlighted with Source Code Highlighter.


Ну что же — уже лучше! Ошибиться при печати XML уже стало существенно сложнее, и производительность осталась на высоте. Последнее, впрочем, не удивительно, так как CAML.NET никак не влияет на производительность, это просто тонкая обертка поверх стандартного CAML синтаксиса — CAML.NET просто помогает подготавливать XML, не более того. Что, впрочем, совсем и совсем не плохо!

Если же присмотреться к получившемуся коду более внимательно, то становиться понятно, что это просто попытка мимикрии под стандартный CAML синтаксис. Как следствие, этот подход наследуют многие исходные проблемы CAML синтаксиса. Из тех проблем, которые я упомянул выше (в описании ко второму подходу), по сути, решается только одна проблема — проблема корректного форматирования XML, в то время как все остальные проблемы остаются. Если пойти на форум CAML.NET проекта, то видно, что пользователи как раз и жалуются на эти проблемы.

Есть ли что-нибудь более удобное для разработчика? Я бы не стал писать эту статью, если бы мой ответ был «нет» :-)

Четвертый подход


Чуть более недели назад мы выложили новый проект под названием Camlex.NET на CodePlex – проект призван решить упомянутые проблемы с разработкой выборок из SharePoint-ских списков — сделать это процесс максимально близким и понятным C# разработчику. Адрес проекта — camlex.codeplex.com/.

Итак, ближе к делу — воспроизведем функциональность предыдущих примеров, но уже на базе проекта Camlex.NET:

  1.     SPQuery query3 = new SPQuery();
  2.     query3.Query = Camlex.Query()
  3.         .Where(x => (string) x["TextField"] == "Test" &&
  4.             ((int) x["IntField"] != -1 || (DateTime) x["DateField"] < DateTime.Now))
  5.         .OrderBy(x => x["SortField"]).ToString();
  6.     SPListItemCollection resultItems3 = list.GetItems(query3);
* This source code was highlighted with Source Code Highlighter.


И это все! :-)

Как можно заметить, этот подход на полную катушку использует expression trees и лямбды, предоставляя возможность разработчикам писать C# код, не забивая при этом голову проблемами использования CAML синтаксиса.

Достоинства этого подхода:
  • Размер кода стал даже меньше, чем в самом первом примере, а фильтры остались такими же, как и там — простыми и понятными.
  • Производительность — на уровне чистого CAML запроса.
  • Возможность использования при выборках из множества списков (вместо SPQuery надо использовать SPSiteDataQuery).
  • Отсутствие XML синтаксиса как такового :-) Любые изменения фильтров — естественны для C# разработчиков, и не потребует перестройки XML дерева.
  • Разработчик остается к контексте C#. А это значит — Intellisense, compile-time проверки, code-first approach, и т.д. и т.п.
В это статье я не стал углубляться в детали проекта Camlex.NET — формат статьи не позволят мне этого сделать, но если сходить на сайт проекта, то можно увидеть еще много других примеров использования. В этой же статье, я просто хотел сделать сравнения разных методик выборок из SharePoint-ских списков — а уж решение что использовать — за вами, уважаемые читатели!

Ссылки


На последок, еще раз адрес нашего проекта:
Camlex.NET на CodePlex'е — http://camlex.codeplex.com/.

Блоги разработчиков:
Алексей Садомов (мой коллега) — http://sadomovalex.blogspot.com/
Владимир Тимашков (ваш покорный слуга)http://vtimashkov.wordpress.com/

Надеюсь, наш проект будет полезен вам в вашей ежедневной борьбе с SharePoint'ом :-)
+6
1296
8
butaji 49,6

комментарии (14)

+5
dieron, #
Я не специалист в SharePoint, но даже мне понятно, что тем, кто сталкивался с описанной проблемой статья очень пригодится. Кроме того, она IMHO очень хорошо написана.

Как же я удивился, когда обнаружил -2 после голосования! Зачем? Люди проделали полезную работу, потом написали статью, чтобы помочь другим, а вы жмете на минус, не читая текст или не разбираясь в вопросе…
+2
oddmanout, #
И статья отличная и продукт весьма полезный. Спасибо за труды.
+1
Olegas, #
Своего рода приближение (по удобству использования) к LINQ to SharePoint
0
Olegas, #
Посоветуйте пожалуйста, можно ли, и как с помощью вашего подхода сгенерировать CAML на основе данных массива.

Т.е. у меня есть Array или List, не важно, с числовыми ID
Нужно собрать конструкцию выбора всех элементов списка у которых ID = элемент списка т.е. объединить несколько условий Or на один и тот же FieldRef но с разыми Value
+1
butaji, #
Ребята сказали, что пока такой функциональности нет, но скорее всего она появится, за дальнейшим развитием событий наблюдайте на camlex.codeplex.com/
0
Olegas, #
Очень жаль. Получается что строить более сложные динамические динамические запросы (чем те, что описаны на codeplex) пока не получится =(

В моих use-case таких, к сожалению, большинство.
+1
butaji, #
0
sadomovalex, #
лучше поздно чем никогда :) Отвечу сейчас, возможно кому-нибудь пригодится:
эта функциональность появилась с версией 2.0. Называется «Dynamic Filtering Conditions»: Camlex.NET 2.0 for Windows SharePoint Services (методы WhereAll и WhereAny).
0
Olegas, #
А что, работа с Guid не поддерживается?
0
butaji, #
Нет, я уже создал запрос
0
sadomovalex, #
поддерживается, также начиная с версии 2.0.
0
butaji, #
0
Olegas, #
Вопрос безотносительно Camlex.NET, скорее вопрос специалистам…

Заметил, что если сделать конструкцию вида (результирующий запрос можно написать и руками, дело не в Camlex)

var q = new SPQuery();
q.Query = Camlex.Where(/*some expression*/).GroupBy(x=>x[«SomeField»]).ToString();
ListInstance.getItems(q);

То группировка GroupBy выполнена не будет. Если же сделать запрос без Where, только с GroupBy — группировка будет работать.

Выглядит так, что GroupBy работает только в одиночку, вместе с другими операторами схемы — игнорируется без каких-либо ошибок и исключений.

Это такая багофича CAML или нужен hands.dll?
0
Alex_BBB, #
Спасибо за проект. Как его использовать в Sandboxed Solution? (продублировал на camlex.codeplex.com/discussions/358131)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.