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

Часть 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. Посему очередной пост будет посвящен работе с ним. На этот раз я покажу как работать с полями типа Choice и MultiChoice.

enum для поля типа Choice

Environment

Все примеры будут основываться на модели из 2 части постов, посвященных Linq to SharePoint. Для демонстрации в этом посте я добавил два поля в тип содержимого Employee. Первое поле - Sex (SPFieldChoice), второе - Hobbies (SPFieldMultiChoice):

  1. <Field ID="{68c1ee4a-5a25-4ccb-82ca-f5ff17e2016f}" Name="Sex" DisplayName="Sex" Type="Choice" Format="RadioButtons">
  2.   <CHOICES>
  3.     <CHOICE>Male</CHOICE>
  4.     <CHOICE>Female</CHOICE>
  5.   </CHOICES>
  6.   <Default>Male</Default>
  7. </Field>
  8. <Field ID="{BD1898B4-0869-41F2-9DB0-B0F1B8F139D3}" Name="Hobbies" DisplayName="Hobbies" Type="MultiChoice" Mult="TRUE">
  9.   <CHOICES>
  10.     <CHOICE>Chess</CHOICE>
  11.     <CHOICE>Football</CHOICE>
  12.     <CHOICE>Basketball</CHOICE>
  13.   </CHOICES>
  14.   <Default>Chess</Default>
  15. </Field>

Теперь, развернув решения и создав код с помощью SPMetal, мы получим для этих полей два перечисления. Один из которых с атрибутом FlagsAttribute (для множественных значений):

  1. public enum EmployeeSex
  2. {
  3.     None = 0,
  4.     Invalid = 1,
  5.     [Choice(Value = "Male")]
  6.     Male = 2,
  7.     [Choice(Value = "Female")]
  8.     Female = 4
  9. }
  10.  
  11. [FlagsAttribute]
  12. public enum EmployeeHobby
  13. {
  14.     None = 0,
  15.     Invalid = 1,
  16.     [Choice(Value = "Chess")]
  17.     Chess = 2,
  18.     [Choice(Value = "Football")]
  19.     Football = 4,
  20.     [Choice(Value = "Basketball")]
  21.     Basketball = 8
  22. }

Single Choice

Начнем с простого случая, когда у нас поле может иметь только одно значение. Имея код, созданный SPMetal, все кажется очень просто. Если нам необходимо выбрать всех сотрудников мужского пола, то логично было бы использовать следующий код:

  1. using (var ctx = new ZhukDataContext(siteUrl))
  2. {
  3.     var employees = ctx.Employees
  4.         .Where(emp => emp.Sex == EmployeeSex.Male)
  5.         .ToList();
  6.     // ...
  7. }

Но в этом случае SPLinqProvider не сможет конвертировать этот запрос в ожидаемый нами CAML. Сначала произойдет выгрузка всех элементов, а уже потом в памяти произойдет фильтрация. Связано это с тем, что поля типа Choice и MultiChoice хранятся в базе данных в виде текстовых значений.

Чтобы обойти это ограничение я использую следующий трюк: я изменяю тип свойства с перечисления на текст. А, чтобы сохранить использование перечислений, можно использовать поле без атрибута ColumnAttribute:

  1. public EmployeeSex? Sex
  2. {
  3.     get
  4.     {
  5.         var res = Enum.Parse(typeof (EmployeeSex), _sexValue);
  6.         return res is EmployeeSex ? (EmployeeSex) res : EmployeeSex.Invalid;
  7.     }
  8. }
  9.  
  10. [Column(Name = "Sex", Storage = "_sexValue", FieldType = "Choice")]
  11. public string SexValue
  12. {
  13.     get
  14.     {
  15.         return _sexValue;
  16.     }
  17.     set
  18.     {
  19.         if ((value == _sexValue)) return;
  20.         var vals = Enum.GetValues(typeof(EmployeeSex));
  21.         foreach (EmployeeSex val in vals)
  22.         {
  23.             if (!string.Equals(Enum.GetName(typeof(EmployeeSex), val), value,
  24.                                 StringComparison.InvariantCultureIgnoreCase)) continue;
  25.             OnPropertyChanging("SexValue", _sexValue);
  26.             _sexValue = value;
  27.             OnPropertyChanged("SexValue");
  28.         }
  29.     }
  30. }

Теперь фильтровать можно по текстовому полю SexValue, получая в результате правильный CAML-запрос. А чтобы избежать использование текстовых значений в коде, можно написать метод расширитель для перечислений, который будет возвращать значение свойства Value атрибута ChoiceAttribute:

  1. public static string GetChoiceValue(this Enum enumerator)
  2. {
  3.     // Получаем тип
  4.     var type = enumerator.GetType();
  5.     // Получаем имя поля
  6.     var fieldName = Enum.GetName(enumerator.GetType(), enumerator);
  7.     // Получаем поле
  8.     var field = type.GetField(fieldName, 
  9.                 BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public);
  10.     // Получаем атрибуты найденного поля
  11.     var attributes = field.GetCustomAttributes(typeof (ChoiceAttribute), true);
  12.     var attribute = attributes.FirstOrDefault();
  13.     // Если атрибута нет, то возвращаем пустую строку
  14.     // Иначе возвращаем значение Value атрибута
  15.     return attribute == null
  16.                 ? string.Empty
  17.                 : ((ChoiceAttribute) attribute).Value;
  18. }

Вот теперь все работает правильно и выглядит довольно красиво:

  1. using (var ctx = new ZhukDataContext(siteUrl))
  2. {
  3.     var employees = ctx.Employees
  4.         .Where(emp => emp.SexValue == EmployeeSex.Male.GetChoiceValue())
  5.         .ToList();
  6. }

Сгенерированный в обоих случаях CAML-запрос я здесь приводить не буду - очень много места он занимает. Все это можно будет попробовать, взяв демонстрационный проект.

Multiple Choice

А вот в этом случае ничего уже не выйдет (по крайней мере, я не придумал, как это обойти). Убежден, что копать надо в сторону того, что все значения полей типа MultiChoice в базе данных обрамлены с обеих сторон разделителем (;#):

Значение полей Choice и MultiChoice в базе данных содержимого SharePoint 2010

Остается только привести значение перечислителя к тексту. И использовать аналог метода расширителя EqualsAny, описанный мною в 4 части. Только наш новый метод будет проверять на содержание значения в строке:

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

Исходный коды демонстрационного проекта можно скачать здесь.

Надеюсь, эта серия постов об использовании 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. Часть 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. Оптимистические блокировки