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.
Создадим конструктор в базовом классе, чтобы эта возможность появилась во всех дочерних классах и, чтобы, в случае создания новых классов, нам не приходилось писать новый код:
- public class ZhukDataItem : ITrackEntityState, ITrackOriginalValues,
- INotifyPropertyChanged, INotifyPropertyChanging
- {
- public ZhukDataItem(SPItem item)
- {
- }
- }
Чтобы заполнить свойства будем использовать атрибут ColumnAttribute наших свойств:
Дополнительно можно проверять на реализацию объектом интерфейса ICustomMapping и вызывать метод MapFrom. Объект мы получили, теперь надо присоединить его к контексту данных. Для этого используем метод Attach:
- using (var ctx = new ZhukDataContext(siteUrl))
- {
- // Получаем объект SPListItem
- SPListItem employeeListItem = GetEmployeeListItem(employeeId);
- // Создаем экземпляр класс Employee
- var entity = new Employee(employeeListItem);
- // Присоединяем объект к контексту
- ctx.Employees.Attach(entity);
-
- // Проводим необходимые манипуляции
-
- // Сохраняем изменения
- ctx.SubmitChanges();
- }
Dynamic Linq to SharePoint
В первой части я писал о том, что Linq to SharePoint не поддерживает Contains(). Теперь я покажу способ это ограничение обойти. Для этого напишем метод расширитель, который будет строить выражения для каждого значения из массива (тот самый IN из T-SQL) и затем объединять их:
- public static Expression<Func<T, bool>> EqualsAny<T, TValue>(this Expression<Func<T, TValue>> selector,
- IEnumerable<TValue> values)
- {
- // Если значений нет, то возвращаем выражение x=> false
- if (!values.Any()) return x => false;
- // Аналогично поступаем в случае, когда кол-во параметров не равно одному
- if (selector.Parameters.Count != 1) return x => false;
- // Берем параметр селектора.
- // Он понадобиться для построения выражений
- var p = selector.Parameters.First();
- // Для каждого значения строим выражение
- var equals = values
- .Select(v => (Expression)Expression.Equal(selector.Body,
- Expression.Constant(v, typeof(TValue))));
- // Объдиняем получившиеся выражения
- var body = equals.Aggregate(Expression.Or);
- // Возвращаям получившиеся выражение
- return Expression.Lambda<Func<T, bool>>(body, p);
- }
Аналогично можно сделать методы для текстовых значение (StartsWitAny, ContainsAny и др.). А вот объединять произвольные выражения у меня не получилось. Чтобы их объединить приходилось пользоваться методом Expression.Invoke. После чего SPLinqProvider выбрасывал исключение: Lambda Parameter not in scope. Аналога методов WhereAny и WhereAll из CamleX'а у меня не получилось.
Использовать новый метод можно даже в достаточно сложной конструкции:
- using (var ctx = new ZhukDataContext(siteUrl))
- {
- var ids = new int?[] { 1, 3, 5, 7, 9, 11, 13, 15 };
- var predicate = EqualsAny<Employee, int?>(emp => emp.Id, ids);
- var employees = ctx.Employees
- .ScopeToFolder(string.Empty, true)
- .Where(emp => emp.ManagerId == 2)
- .Where(predicate)
- .Where(emp => emp.AccessLevel > 2)
- .OrderBy(emp => emp.Title)
- .Take(5)
- .ToList();
- //...
- }
В итоге получается большой CAML-запрос, позволяющий получать только те данные, которые необходимы и тем самым повысить производительность:
- <View Scope='RecursiveAll'>
- <Query>
- <Where>
- <And>
- <And>
- <And>
- <BeginsWith>
- <FieldRef Name="ContentTypeId" />
- <Value Type="ContentTypeId">0x010078B0DD38574940478CF9E129FCD65E9B</Value>
- </BeginsWith>
- <Eq><FieldRef Name="Manager" LookupId="TRUE" /><Value Type="Lookup">2</Value></Eq>
- </And>
- <Or>
- <Or>
- <Or>
- <Or>
- <Or>
- <Or>
- <Or>
- <Eq><FieldRef Name="ID" /><Value Type="Counter">1</Value></Eq>
- <Eq><FieldRef Name="ID" /><Value Type="Counter">3</Value></Eq>
- </Or>
- <Eq><FieldRef Name="ID" /><Value Type="Counter">5</Value></Eq>
- </Or>
- <Eq><FieldRef Name="ID" /><Value Type="Counter">7</Value></Eq>
- </Or>
- <Eq><FieldRef Name="ID" /><Value Type="Counter">9</Value></Eq>
- </Or>
- <Eq><FieldRef Name="ID" /><Value Type="Counter">11</Value></Eq>
- </Or>
- <Eq><FieldRef Name="ID" /><Value Type="Counter">13</Value></Eq>
- </Or>
- <Eq><FieldRef Name="ID" /><Value Type="Counter">15</Value></Eq>
- </Or>
- </And>
- <Gt><FieldRef Name="AccessLevel" /><Value Type="Integer">2</Value></Gt>
- </And>
- </Where>
- <OrderBy Override="TRUE"><FieldRef Name="Title" /></OrderBy>
- </Query>
- <ViewFields>
- <FieldRef Name="CellPhone" />
- <FieldRef Name="AccessLevel" />
- <FieldRef Name="Manager" />
- <FieldRef Name="Department" />
- <FieldRef Name="ID" />
- <FieldRef Name="owshiddenversion" />
- <FieldRef Name="FileDirRef" />
- <FieldRef Name="Title" />
- <FieldRef Name="Author" />
- <FieldRef Name="Editor" />
- </ViewFields>
- <RowLimit Paged="TRUE">5</RowLimit>
- </View>
Осталось, пожалуй, только описать работу с оптимистическими блокировками в Linq to SharePoint. В ближайшее время напишу.