Linq to SharePoint. Особенности. Часть 2
Часть 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
Для демонстрации я создал простой проект, в котом описаны 4 типа содержимого (Сотрудник, Департамент, Филиал, Компания), поля для них, списки, и классы для этих сущностей:
Трассировка
Во-первых, для проверки построения CAML-запросов нам понадобится трассировщик. Вот пример того, который использовал я:
public class ZhukBlogTracer : TextWriter
{
public override Encoding Encoding
{
get { return Encoding.UTF8; }
}
public override void WriteLine(string value)
{
Debug.WriteLine(string.Empty);
Debug.WriteLine("---------------------------------------------------------");
Debug.WriteLine(value);
Debug.WriteLine("---------------------------------------------------------");
}
public override void Flush()
{
Debug.Flush();
}
}
Он просто выводит построенный CAML в окно Output Visual Studio. Этого для отладки вполне достаточно. Чтобы его задействовать надо просто подставить экземляр этого класса в свойство Log контекста.
Count()
Этот метод-расширитель "не знаком" LinqToSharePoint, т.е. сначала будет неявно получены все элементы, а потом уже произойдет вызов Count(). Но это уже будет Linq to Objects. Если же количество посчитать необходимо, то надо ограничить вывод полей. В случае с количеством будет достаточно выбрать поле ID элемента.
var branchQnt = ctx.Branches
.Where(b => b.City != "Moscow")
.Count(); // LINQ to Objects !
var branchQntQuick = ctx.Branches
.Where(b => b.City != "Moscow")
.Select(b => b.Id) // ViewFields
.Count();
В результате будет построен следующий CAML:
<View>
<Query>
<Where>
<And>
<BeginsWith>
<FieldRef Name="ContentTypeId" />
<Value Type="ContentTypeId">0x01007087DA17F20149E3A4602E517E6E8EEF</Value>
</BeginsWith>
<Neq>
<FieldRef Name="City" />
<Value Type="Text">Moscow</Value>
</Neq>
</And>
</Where>
</Query>
<ViewFields>
<FieldRef Name="ID" />
</ViewFields>
<RowLimit Paged="TRUE">2147483647</RowLimit>
</View>
Take(n), Skip(n)
Здесь тоже все не так просто. CAML-запрос имеет параметр RowLimit который ограничивает количество выбираемых элементов, т.е. следующий код укажет RowLimit равным 10:
var branchTake = ctx.Branches
.Where(b => b.City != "Moscow")
.Take(10)
.Count();
Что касается метода Skip, то перед его выполнением будет неявно получены все элементы. Привычный последовательный вызов методов Skip и Take вообще не имеет никакого отношений к Linq to SharePoint:
var branchSkipTake = ctx.Branches
.Where(b => b.City != "Moscow")
.Skip(10) // LINQ to Objects !
.Take(10); // LINQ to Objects !
Если у вас есть функционал с постраничным выводом информации, то максимум, что вы можете сделать, так это поменять местами вызовы этих методов и надеется, что пользователь не полезет дальше 5 страницы:
// pageIndex - индекс текущей страницы
// pageSize - кол-во элементов на странице
var branchPaged = ctx.Branches
.Where(b => b.City != "Moscow")
.Take((pageIndex + 1) * pageSize)
.Skip(pageIndex * pageSize); // LINQ to Objects !
JOIN'ы
JOIN'ы, которые появились в SharePoint 2010 строятся только на основе EntityRef свойств ваших объектов. Задействовать этот функционал вызовом метода-расширителя Join() не получиться. К тому же здесь есть ограничение: ссылаться можно только на одну сущность за один запрос (если надо больше - придется выгружать объекты в память и фильтровать уже там). Вот простой пример
var bigJoin = ctx.Employees
.Where(x => x.Department.Title.Contains("IT"))
.Where(x => x.Title.Contains("Zhukov"))
.ToList();
И долгожданный JOIN в CAML-запросе:
<View>
<Query>
<Where>
<And>
<And>
<BeginsWith>
<FieldRef Name="ContentTypeId" />
<Value Type="ContentTypeId">0x010078B0DD38574940478CF9E129FCD65E9B</Value>
</BeginsWith>
<Contains>
<FieldRef Name="DepartmentTitle" />
<Value Type="Lookup">IT</Value>
</Contains>
</And>
<Contains>
<FieldRef Name="Title" />
<Value Type="Text">Zhukov</Value>
</Contains>
</And>
</Where>
</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>
<ProjectedFields>
<Field Name="DepartmentTitle" Type="Lookup" List="Department" ShowField="Title" />
</ProjectedFields>
<Joins>
<Join Type="LEFT" ListAlias="Department">
<!--List Name: Departments-->
<Eq>
<FieldRef Name="Department" RefType="ID" />
<FieldRef List="Department" Name="ID" />
</Eq>
</Join>
</Joins>
<RowLimit Paged="TRUE">2147483647</RowLimit>
</View>
ObjectTrackingEnabled
И напоследок я расскажу о замечательном свойстве ObjectTrackingEnabled контекста данных. Это флаг, указывающий на необходимость отслеживания изменений свойств объектов. В случае, если вы используете Linq to SharePoint в режиме только для чтения обязательно указываете это свойство равным false для повышения производительности.