Linq to SharePoint. Сравнение производительности с Camlex.NET

Часть 1. First()/FirstOrDefault(), T-SQL IN, Path
Часть 2. Count(), Take(), Skip(), JOIN
Часть 3. Анонимный доступ, Получение списка по URL'у, Cross-Site запросы
Часть 4. SPListItem -> LINQ, Dynamic Linq to SharePoint
Часть 5. Поля Choice и MultiChoice в Linq to SharePoint
Часть 6. Сравнение производительности Linq to SharePoint и Camlex.NET

Еще один пост, посвященный работе с Linq to SharePoint. На этот раз я решил измерить скорость работы с данными и сравнить производительность Linq to SharePoint и Camlex.NET.

Получение данных из списка

Для измерения производительности я опять же использовал модель данных из второй части постов, посвященных Linq to SharePoint. Время, потраченное на выборку я измерял с помощью класса System.Diagnostics.Stopwatch. Саму выборку проводил несколько раз как при "холодном" старте, так и при "горячем". Запросы были максимально сложными и идентичными (насколько это возможно). JOIN'ы я использовал только для того, чтобы иметь возможность отфильтровать данные по LookupValue. Не думаю, что это сильно отразится на результатах (Camlex.NET, не умеет строить JOIN'ы). Предварительно я сгенерировал 20 тысяч записей в списке Employees.

И так, запрос, который был передан Linq to SharePoint:

  1. using (var ctx = new ZhukDataContext(siteUrl))
  2. {
  3.     var exEmpIds = new int?[] { 1, 2, 3, 4, 5 };
  4.     var query = ctx.Employees
  5.         .ScopeToFolder(string.Empty, true)
  6.         .Where(emp => emp.SexValue == "Male")
  7.         .Where(emp => emp.Title.Contains("Jr."))
  8.         .Where(emp => emp.Department.Title != "IT")
  9.         .Where(Extensions.NotEqualsAll<Employee, int?>(i => i.Id, exEmpIds))
  10.         .OrderBy(emp => emp.Title)
  11.         .Take(100);
  12.     var items = query.ToList();
  13. }

Немного пояснений:

  • exEmpIds - массив Id сотрудников, которые должны быть исключены;
  • SexValue - поле типа "выбор" (choice);
  • Title.Contains - вызов метода Contains в CAML-запросе;
  • Department.Title - фильтрация по LookupValue;

Аналогичный запрос в Camlex.NET будет выглядеть менее изящно:

  1. using (var site = new SPSite(siteUrl))
  2. {
  3.     using (var web = site.OpenWeb())
  4.     {
  5.         var list = web.Lists["Employees"];
  6.         var expressions = new List<Expression<Func<SPListItem, bool>>>();
  7.         expressions.Add(x => (string) x["Sex"] == "Male");
  8.         expressions.Add(x => ((string) x["Title"]).Contains("Jr."));
  9.         expressions.Add(x => x["Department"] != (DataTypes.LookupValue) "IT");
  10.         foreach (var empId in exEmpIds)
  11.         {
  12.             int id = empId.Value;
  13.             expressions.Add(x => (int) x["ID"] != id);
  14.         }
  15.         var caml = Camlex.Query()
  16.             .WhereAll(expressions)
  17.             .OrderBy(x => x["Title"]);
  18.         var query = new SPQuery
  19.                         {
  20.                             RowLimit = 100,
  21.                             Query = caml.ToString()
  22.                         };
  23.         var items = list.GetItems(query)
  24.             .Cast<SPListItem>()
  25.             .ToList();
  26.  
  27.     }
  28. }

Для начала необходимо посмотреть CAML-запросы, которые будут сгенерированы для получения элементов списка. Сначала Linq to SharePoint:

  1. <View Scope='RecursiveAll'>
  2.   <Query>
  3.     <Where>
  4.       <And>
  5.         <And>
  6.           <And>
  7.             <And>
  8.               <BeginsWith>
  9.                 <FieldRef Name="ContentTypeId" />
  10.                 <Value Type="ContentTypeId">0x010078B0DD38574940478CF9E129FCD65E9B</Value>
  11.               </BeginsWith>
  12.               <Eq>
  13.                 <FieldRef Name="Sex" />
  14.                 <Value Type="Choice">Male</Value>
  15.               </Eq>
  16.             </And>
  17.             <Contains>
  18.               <FieldRef Name="Title" />
  19.               <Value Type="Text">Jr.</Value>
  20.             </Contains>
  21.           </And>
  22.           <Neq>
  23.             <FieldRef Name="DepartmentTitle" />
  24.             <Value Type="Lookup">IT</Value>
  25.           </Neq>
  26.         </And>
  27.         <And>
  28.           <And>
  29.             <And>
  30.               <And>
  31.                 <Neq>
  32.                   <FieldRef Name="ID" />
  33.                   <Value Type="Counter">1</Value>
  34.                 </Neq>
  35.                 <Neq>
  36.                   <FieldRef Name="ID" />
  37.                   <Value Type="Counter">2</Value>
  38.                 </Neq>
  39.               </And>
  40.               <Neq>
  41.                 <FieldRef Name="ID" />
  42.                 <Value Type="Counter">3</Value>
  43.               </Neq>
  44.             </And>
  45.             <Neq>
  46.               <FieldRef Name="ID" />
  47.               <Value Type="Counter">4</Value>
  48.             </Neq>
  49.           </And>
  50.           <Neq>
  51.             <FieldRef Name="ID" />
  52.             <Value Type="Counter">5</Value>
  53.           </Neq>
  54.         </And>
  55.       </And>
  56.     </Where>
  57.     <OrderBy Override="TRUE">
  58.       <FieldRef Name="Title" />
  59.     </OrderBy>
  60.   </Query>
  61.   <ViewFields>
  62.     <FieldRef Name="Sex" />
  63.     <FieldRef Name="CellPhone" />
  64.     <FieldRef Name="AccessLevel" />
  65.     <FieldRef Name="Manager" />
  66.     <FieldRef Name="Department" />
  67.     <FieldRef Name="ID" />
  68.     <FieldRef Name="owshiddenversion" />
  69.     <FieldRef Name="FileDirRef" />
  70.     <FieldRef Name="Title" />
  71.     <FieldRef Name="Author" />
  72.     <FieldRef Name="Editor" />
  73.   </ViewFields>
  74.   <ProjectedFields>
  75.     <Field Name="DepartmentTitle" Type="Lookup" List="Department" ShowField="Title" />
  76.   </ProjectedFields>
  77.   <Joins>
  78.     <Join Type="LEFT" ListAlias="Department">
  79.       <!--List Name: Departments-->
  80.       <Eq>
  81.         <FieldRef Name="Department" RefType="ID" />
  82.         <FieldRef List="Department" Name="ID" />
  83.       </Eq>
  84.     </Join>
  85.   </Joins>
  86.   <RowLimit Paged="TRUE">100</RowLimit>
  87. </View>

Здесь все правильно. Теперь очередь Camlex.NET:

  1. <Where>
  2.   <And>
  3.     <And>
  4.       <And>
  5.         <And>
  6.           <And>
  7.             <And>
  8.               <And>
  9.                 <Eq>
  10.                   <FieldRef Name="Sex" />
  11.                   <Value Type="Text">Male</Value>
  12.                 </Eq>
  13.                 <Contains>
  14.                   <FieldRef Name="Title" />
  15.                   <Value Type="Text">Jr.</Value>
  16.                 </Contains>
  17.               </And>
  18.               <Neq>
  19.                 <FieldRef Name="Department" />
  20.                 <Value Type="Lookup">IT</Value>
  21.               </Neq>
  22.             </And>
  23.             <Neq>
  24.               <FieldRef Name="ID" />
  25.               <Value Type="Integer">5</Value>
  26.             </Neq>
  27.           </And>
  28.           <Neq>
  29.             <FieldRef Name="ID" />
  30.             <Value Type="Integer">4</Value>
  31.           </Neq>
  32.         </And>
  33.         <Neq>
  34.           <FieldRef Name="ID" />
  35.           <Value Type="Integer">3</Value>
  36.         </Neq>
  37.       </And>
  38.       <Neq>
  39.         <FieldRef Name="ID" />
  40.         <Value Type="Integer">2</Value>
  41.       </Neq>
  42.     </And>
  43.     <Neq>
  44.       <FieldRef Name="ID" />
  45.       <Value Type="Integer">1</Value>
  46.     </Neq>
  47.   </And>
  48. </Where>

Тоже хорошо. Есть недочеты, но на скорость они не повлияют. Теперь осталось измерить скорость выбора данных. Вот мои результаты для "холодного" и "горячего" стартов соответственно:

На "холодном" старте разница невелика, т.к. в обоих случаях происходит инициализация объектов SPSite, SPWeb, SPList и других. На "горячем" старте, когда объекты закэшированы очевидно виден лидер - Linq to SharePoint.

Тест под нагрузкой

Дальше я измерял производительность Camlex.NET в двух режимах: просто выбор элементов из списка и минимальное преобразование SPlistItem в Employee ( new Employee { Title = item["Title"] }). В первом случае я пытался добиться максимальной производительности, а во втором случае приблизил сценарий к реальной жизни. При этом я повышал количество выбираемых элементов, начиная с 10 и заканчивая 2500. Результаты производительности Camlex.NET под нагрузкой:

Производительность Camlex.NET в зависимости от нагрузки

Для Linq to SharePoint я придумал два других варианта работы. Первый в режиме ReadOnly (ObjectTrackingEnabled = false), второй - с включенным трекингом. Все остальные параметры были идентичны Camlex.NET. Результаты производительности Linq to SharePoint под нагрузкой:

Производительность Linq to SharePoint в зависимости от нагрузки

Если свести все вместе для удобства сравнения:

Производительность Camlex.NET и Linq to SharePoint в зависимости от нагрузки

Выводы

Выводы просты и очевидны: Linq to SharePoint не только работает быстрее, но и обладает большими возможностями по сравнению с Camlex.NET. Используя Linq to SharePoint мы получаем типизированную коллекцию, в случае с Camlex.NET коллекцию объектов SPListItem, которую ещё предстоит "замапить" на объекты.

Также хочу отметить влияние трекинга на скорость чтения данных Linq to SharePoint из списка. Обязательно отключайте его, если ваш контекст используется только для чтения данных.

Виталий Жуков

Виталий Жуков

Техлид, Архитектор, Разработчик, Microsoft MVP. Более 20 лет опыта в области системной интеграции и разработки программного обеспечения. Специализируюсь на проектировании и внедрении масштабируемых высокопроизводительных программных решений в различных отраслях.

Смотрите также

Развертывание списков и библиотек с помощью SPFx-решений

Развертывание списков и библиотек с помощью SPFx-решений

SharePoint. Drag-and-Drop Загрузчик файлов

SharePoint. Drag-and-Drop Загрузчик файлов

CSOM. Загрузка файлов

CSOM. Загрузка файлов

SharePoint List REST API. Часть 2

SharePoint List REST API. Часть 2

SharePoint Framework. Создание веб-части на Angular

SharePoint Framework. Создание веб-части на Angular

SharePoint List REST API. Часть 1

SharePoint List REST API. Часть 1

Презентация с доклада о SharePoint Framework

Презентация с доклада о SharePoint Framework

SharePoint Framework. Создаем AngularJS 1.x Client WebPart

SharePoint Framework. Создаем AngularJS 1.x Client WebPart

SharePoint. Регистрация CSS и JavaScript с помощью DelegateControl

SharePoint. Регистрация CSS и JavaScript с помощью DelegateControl

SharePoint. Расширяем REST API

SharePoint. Расширяем REST API

SharePoint Excel Services. Создаем кредитный калькулятор

SharePoint Excel Services. Создаем кредитный калькулятор

SharePoint Ribbon API. Использование ToggleButton

SharePoint Ribbon API. Использование ToggleButton

SharePoint 2013. How To: настройка входящей почты для разработчиков

SharePoint 2013. How To: настройка входящей почты для разработчиков

Мифы и правда о Linq to SharePoint

Мифы и правда о Linq to SharePoint

5 особенностей SPSiteDataQuery

5 особенностей SPSiteDataQuery

SharePoint 2013. Введение в SharePoint App. Часть 2

SharePoint 2013. Введение в SharePoint App. Часть 2

SharePoint 2013. Введение в SharePoint App. Часть 1

SharePoint 2013. Введение в SharePoint App. Часть 1

Превью для веб-части в SharePoint 2010/2013

Превью для веб-части в SharePoint 2010/2013

SharePoint 2013. Еще немного о новых контролах

SharePoint 2013. Еще немного о новых контролах

SharePoint 2013. Контрол ClientPeoplePicker

SharePoint 2013. Контрол ClientPeoplePicker

SharePoint 2013. Контрол ImageCrop

SharePoint 2013. Контрол ImageCrop

SharePoint 2013. Тип поля Geolocation

SharePoint 2013. Тип поля Geolocation

Создание типа поля в SharePoint

Создание типа поля в SharePoint

SharePoint 2010. Длительные операции с обновляемым статусом

SharePoint 2010. Длительные операции с обновляемым статусом

Linq to SharePoint. Создаем ContentIterator

Linq to SharePoint. Создаем ContentIterator

Linq to SharePoint. Получение данных из другой коллекции сайтов

Linq to SharePoint. Получение данных из другой коллекции сайтов

Linq to SharePoint. Версионность

Linq to SharePoint. Версионность

SharePoint. Получение URL-адреса иконки для документа

SharePoint. Получение URL-адреса иконки для документа

SharePoint 2010. PostBack для Fluent Ribbon API

SharePoint 2010. PostBack для Fluent Ribbon API

Linq to SharePoint. Блокировка документов

Linq to SharePoint. Блокировка документов

Linq to SharePoint. Паттерн Repository

Linq to SharePoint. Паттерн Repository

Linq to SharePoint. Получение мета-данных списка

Linq to SharePoint. Получение мета-данных списка

Linq to SharePoint. Мапинг полей

Linq to SharePoint. Мапинг полей

Linq to SharePoint. Формирование данных для ProcessBatchData

Linq to SharePoint. Формирование данных для ProcessBatchData

Linq to SharePoint. Часть 5. Поля Choice и MultiChoice

Linq to SharePoint. Часть 5. Поля Choice и MultiChoice

Linq to SharePoint. Часть 4. Dynamic LINQ

Linq to SharePoint. Часть 4. Dynamic LINQ

Linq to SharePoint. Особенности. Часть 3

Linq to SharePoint. Особенности. Часть 3

Linq to SharePoint. Особенности. Часть 2

Linq to SharePoint. Особенности. Часть 2

SharePoint 2010. PeopleEditor. Установка значения

SharePoint 2010. PeopleEditor. Установка значения

SharePoint 2010. Настройка входящей почты для кастомного списка

SharePoint 2010. Настройка входящей почты для кастомного списка

Linq to Sharepoint. Особенности

Linq to Sharepoint. Особенности

EntityFramework. Оптимистические блокировки

EntityFramework. Оптимистические блокировки