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

Часть 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 и SPListItem

Предположим, что у нас есть очень сложный CAML-запрос, который откуда только не выбирает данные с безумным фильтром. Или, что более реально, у нас есть SPListItem в EventReceiver'е. И в обоих случаях мы хотим получить объект ZhukBlogLinqExamples.Model.Employee.

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

  1. public class ZhukDataItem : ITrackEntityState, ITrackOriginalValues,
  2.                             INotifyPropertyChanged, INotifyPropertyChanging
  3. {
  4.     public ZhukDataItem(SPItem item)
  5.     {
  6.     }
  7. }

Чтобы заполнить свойства будем использовать атрибут ColumnAttribute наших свойств:

  1. public ZhukDataItem(SPItem item)
  2. {
  3.     if (item == nullreturn;
  4.     // Текущий тип
  5.     var objType = GetType();
  6.     // Получаем свойства текущего объекта
  7.     var properties = objType.GetProperties();
  8.     foreach (var property in properties)
  9.     {
  10.         // Получаем атрибуты типа ColumnAttribute
  11.         var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
  12.         foreach (ColumnAttribute att in attributes)
  13.         {
  14.             // Берем поле для хранения значения, 
  15.             // указанное в атрибуте ColumnAttribute
  16.             var field = objType.GetField(att.Storage, 
  17.                 BindingFlags.NonPublic | BindingFlags.Instance);
  18.             // Если такого поля в классе нет, то просматриваем базовые классы
  19.             while (field == null
  20.             {
  21.                 objType = objType.BaseType;
  22.                 if (objType == nullbreak;
  23.                 field = objType.GetField(att.Storage, 
  24.                     BindingFlags.NonPublic | BindingFlags.Instance);
  25.             }
  26.             if (field != null)
  27.             {
  28.                 // Если поле Lookup (LookupId;#LookupValue), то разбираем его
  29.                 if (att.FieldType == "Lookup")
  30.                 {
  31.                     try
  32.                     {
  33.                         var fv = new SPFieldLookupValue(
  34.                             (item[att.Name] ?? string.Empty).ToString());
  35.                         if (att.IsLookupId)
  36.                         {
  37.                             field.SetValue(this, fv.LookupId);
  38.                         }
  39.                         else
  40.                         {
  41.                             field.SetValue(this, fv.LookupValue);
  42.                         }
  43.                     }
  44.                     catch (ArgumentException) // Если Lookup поле равно null
  45.                     {
  46.                         field.SetValue(this, item[att.Name]);
  47.                     }
  48.                 }
  49.                 else
  50.                 {
  51.                     // Остальные поля записываем как есть
  52.                     field.SetValue(this, item[att.Name]);
  53.                 }
  54.             }
  55.         }
  56.     }
  57. }

Дополнительно можно проверять на реализацию объектом интерфейса ICustomMapping и вызывать метод MapFrom. Объект мы получили, теперь надо присоединить его к контексту данных. Для этого используем метод Attach:

  1. using (var ctx = new ZhukDataContext(siteUrl))
  2. {
  3.     // Получаем объект SPListItem
  4.     SPListItem employeeListItem = GetEmployeeListItem(employeeId);
  5.     // Создаем экземпляр класс Employee
  6.     var entity = new Employee(employeeListItem);
  7.     // Присоединяем объект к контексту
  8.     ctx.Employees.Attach(entity);
  9.     
  10.     // Проводим необходимые манипуляции
  11.  
  12.     // Сохраняем изменения
  13.     ctx.SubmitChanges();
  14. }

Dynamic Linq to SharePoint

В первой части я писал о том, что Linq to SharePoint не поддерживает Contains(). Теперь я покажу способ это ограничение обойти. Для этого напишем метод расширитель, который будет строить выражения для каждого значения из массива (тот самый IN из T-SQL) и затем объединять их:

  1. public static Expression<Func<T, bool>> EqualsAny<T, TValue>(this Expression<Func<T, TValue>> selector, 
  2.     IEnumerable<TValue> values)
  3. {
  4.     // Если значений нет, то возвращаем выражение x=> false
  5.     if (!values.Any()) return x => false;
  6.     // Аналогично поступаем в случае, когда кол-во параметров не равно одному
  7.     if (selector.Parameters.Count != 1) return x => false;
  8.     // Берем параметр селектора.
  9.     // Он понадобиться для построения выражений
  10.     var p = selector.Parameters.First();
  11.     // Для каждого значения строим выражение
  12.     var equals = values
  13.         .Select(v => (Expression)Expression.Equal(selector.Body, 
  14.                                  Expression.Constant(v, typeof(TValue))));
  15.     // Объдиняем получившиеся выражения
  16.     var body = equals.Aggregate(Expression.Or);
  17.     // Возвращаям получившиеся выражение
  18.     return Expression.Lambda<Func<T, bool>>(body, p);
  19. }

Аналогично можно сделать методы для текстовых значение (StartsWitAny, ContainsAny и др.). А вот объединять произвольные выражения у меня не получилось. Чтобы их объединить приходилось пользоваться методом Expression.Invoke. После чего SPLinqProvider выбрасывал исключение: Lambda Parameter not in scope. Аналога методов WhereAny и WhereAll из CamleX'а у меня не получилось.

Использовать новый метод можно даже в достаточно сложной конструкции:

  1. using (var ctx = new ZhukDataContext(siteUrl))
  2. {
  3.     var ids = new int?[] { 1, 3, 5, 7, 9, 11, 13, 15 };
  4.     var predicate = EqualsAny<Employee, int?>(emp => emp.Id, ids);
  5.     var employees = ctx.Employees
  6.         .ScopeToFolder(string.Empty, true)
  7.         .Where(emp => emp.ManagerId == 2)
  8.         .Where(predicate)
  9.         .Where(emp => emp.AccessLevel > 2)
  10.         .OrderBy(emp => emp.Title)
  11.         .Take(5)
  12.         .ToList();
  13.     //...
  14. }

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

  1. <View Scope='RecursiveAll'>
  2.   <Query>
  3.     <Where>
  4.       <And>
  5.         <And>
  6.           <And>
  7.             <BeginsWith>
  8.               <FieldRef Name="ContentTypeId" />
  9.               <Value Type="ContentTypeId">0x010078B0DD38574940478CF9E129FCD65E9B</Value>
  10.             </BeginsWith>
  11.             <Eq><FieldRef Name="Manager" LookupId="TRUE" /><Value Type="Lookup">2</Value></Eq>
  12.           </And>
  13.           <Or>
  14.             <Or>
  15.               <Or>
  16.                 <Or>
  17.                   <Or>
  18.                     <Or>
  19.                       <Or>
  20.                         <Eq><FieldRef Name="ID" /><Value Type="Counter">1</Value></Eq>
  21.                         <Eq><FieldRef Name="ID" /><Value Type="Counter">3</Value></Eq>
  22.                       </Or>
  23.                       <Eq><FieldRef Name="ID" /><Value Type="Counter">5</Value></Eq>
  24.                     </Or>
  25.                     <Eq><FieldRef Name="ID" /><Value Type="Counter">7</Value></Eq>
  26.                   </Or>
  27.                   <Eq><FieldRef Name="ID" /><Value Type="Counter">9</Value></Eq>
  28.                 </Or>
  29.                 <Eq><FieldRef Name="ID" /><Value Type="Counter">11</Value></Eq>
  30.               </Or>
  31.               <Eq><FieldRef Name="ID" /><Value Type="Counter">13</Value></Eq>
  32.             </Or>
  33.             <Eq><FieldRef Name="ID" /><Value Type="Counter">15</Value></Eq>
  34.           </Or>
  35.         </And>
  36.         <Gt><FieldRef Name="AccessLevel" /><Value Type="Integer">2</Value></Gt>
  37.       </And>
  38.     </Where>
  39.     <OrderBy Override="TRUE"><FieldRef Name="Title" /></OrderBy>
  40.   </Query>
  41.   <ViewFields>
  42.     <FieldRef Name="CellPhone" />
  43.     <FieldRef Name="AccessLevel" />
  44.     <FieldRef Name="Manager" />
  45.     <FieldRef Name="Department" />
  46.     <FieldRef Name="ID" />
  47.     <FieldRef Name="owshiddenversion" />
  48.     <FieldRef Name="FileDirRef" />
  49.     <FieldRef Name="Title" />
  50.     <FieldRef Name="Author" />
  51.     <FieldRef Name="Editor" />
  52.   </ViewFields>
  53.   <RowLimit Paged="TRUE">5</RowLimit>
  54. </View>

Осталось, пожалуй, только описать работу с оптимистическими блокировками в Linq to SharePoint. В ближайшее время напишу.

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

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

SharePoint архитектор, разработчик, тренер, Microsoft MVP (Office Development). Более 15 лет опыта работы с SharePoint, Dynamics CRM, Office 365, и другими продуктами и сервисами Microsoft.

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

Развертывание списков и библиотек с помощью 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. Сравнение производительности с Camlex.NET

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

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

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

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. Оптимистические блокировки