Использование лямбд для построения 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'ом :-)
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

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

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

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

                В моих use-case таких, к сожалению, большинство.
          • 0
            А что, работа с Guid не поддерживается?
          • 0
            Вопрос безотносительно 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
              Спасибо за проект. Как его использовать в Sandboxed Solution? (продублировал на camlex.codeplex.com/discussions/358131)

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