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 для повышения производительности.

Теги

Поделиться

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

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

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