SharePoint 2010: LINQ

Введение

Сегодня я расскажу о наиболее симпатичной для разработчиков возможности появившейся с приходом 2010-ой версии SharePoint, это механизм преобразования данных под названием LINQ to SharePoint.

Основным назначением данного инструментария является предоставить возможность разработчикам взаимодействовать со строго-типизированными данными, пользоваться при построении запросов к данным языковыми возможностями (LINQ). Давайте разберемся что же скрывается за этими словами.

Модель разработки (как было до LINQ to SharePoint)

SPListItem

Хочу напомнить вам, что основная работа с данными при разработки на SharePoint происходит через слаботипизированные сущности:

SPListItem listItem = GetLastItem(); 
Console.WriteLine(listItem["Название"]); 

Хочу обратить ваше внимание на работу с элементом SPListItem.

Многие разработчики обращаются к данным по Field Display Name, этот подход, хоть и присутствует во многих примерах на MSDN, но хранит в себе подводные камни: что случится, если ваше поле кто-нибудь захочет переименовать?

Альтернативным подходом является обращение по номеру столбца Internal Name, которое отменяет проблему изменения имени поля, однако оставляет за собой вопрос времени поиска поля по его внутреннему имени, а так же возможность ошибки.

Последним вариантом остается Field Id. Это наиболее приемлемый способ, т.к. он гарантирует, что вы получите в результате интересующее вас поле. Здесь я хочу напомнить вам, что все идентификаторы стандартных SharePoint полей вынесены в класс-справочник SPBuiltInFieldId.

Если же вы создаете свои поля в ваших решениях, то не составляет никакой проблемы создать подобные справочники и пользоваться ими при доступе к данным. Можно делать это собственноручно, а можно сделать шаблон Visual Studio T4, с помощью которого генерировать классы справочники по описаниям полей в ваших фичах (features). Выглядеть он может примерно так:

<#@ template language=«C#v3.5» #>
<#@ output extension=«cs» #>
<#@ Assembly Name=«Microsoft.SharePoint» #>
<#@ Import Namespace=«System.Collections.Generic» #>
<#@ Import Namespace=«Microsoft.SharePoint» #>
<#
string groupName = «My group»;
string siteCollectionUrl = «http://sharepoint&#187;;
#>
using System;

namespace SharePointSolution.Custom
{
  public static class FieldId
  {
<#
  using (SPSite site = new SPSite(siteCollectionUrl))
  {
    SPWeb web = site.RootWeb;
    SPFieldCollection fields = web.Fields;
    foreach (SPField f in fields)
    {
      if (f.Group.Equals(groupName))
      {
#>
    /// <summary>
    /// <para>Title: <#= f.Title #></para>
    /// <para>InternalName: <#= f.InternalName #></para>
    /// <para>Id: <#= f.Id.ToString() #></para>
    /// </summary>
<#
        if (f.TypeAsString.Equals(«LookupMulti») || f.TypeAsString.Equals(«Lookup»))
        {
#>
    public static string <#= f.InternalName #> = «<#= f.Title #>»;
<#
        }
        else
        {
#>
    public static Guid <#= f.InternalName #> = new Guid(«<#= f.Id.ToString() #>»);
<#
        }
      }
    }
  }
#>
  }
}

Кстати подобные шаблоны можно сгенерировать и для Типов Содержимого (ContentTypes), а так же для путей ваших UserControl’ов и проч. Примеры можно скачать здесь: http://imtech.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=31092

Тем не менее результатом listItem[“Title”] будет являться Object, что вынуждает нас производить многочисленные операции по преобразованию данных, а так же многочисленные проверки на неравенство null и прочим ограничениям.

CAML

Так же работа с данными частенько требует построения запросов к данным, данная операция так же не слишком элегантно реализована на SharePoint, реализуется она с помощью языка запросов CAML, являющегося подмножеством XML.

Ограничения  данного языка (невозможность постраничных запросов), а так же отсутствие плюсов строгой типизации подталкивает многих разработчиков к написанию вспомогательных утилит, для упрощения написания и тестирования CAML запросов:

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

Модель разработки с LINQ

Прелюдия

Обычно в разработке своих проектов я стараюсь выделить некоторую модель предметной области, решением задач которой будет заниматься моё приложение. Обычно сущности данной модели выражены с помощью кода на языке программирования (к примеру C#). Где будет храниться эта модель меня чаще всего не сильно беспокоит (т.к. хранилище может варьироваться в зависимости от эффективности использования и требований: к примеру это может быть SQLite, Microsoft SQL Server, SharePoint Services, Web Services).

В настоящее время занимаясь разработкой SharePoint решений мне приходится описывать отображение данных, сохраняемых с списках на сущности моей модели. Делается это с помощью написанных собственноручно преобразователей (http://github.com/butaji/Sapphire). Это удобно, однако не решает всех трудностей (в том числе и построения запросов), в связи с этим всё таки я ожидаю прихода LINQ to SharePoint.

LINQ to SharePoint от сообщества

Появление данного инструментария в SharePoint Services 2010 было вполне ожидаемо, однако некоторые разработчики делали попытки сделать LINQ провайдеры ещё несколько лет назад:

  • http://linqtosharepoint.codeplex.com/ – самая первая попытка реализации LINQ to SharePoint, его создатель Bart De Smet – профессиональный разработчик, в связи с чем у меня есть предположение, что скорее всего команда SharePoint воспользовалась начинаниями его проекта. Данный проект не закончен (последняя версия 0.2.4 от 30 ноября 2007 года), однако он вызвал резонанс в своё время. Рассказ Барта о его проекте можно увидеть на Channel 9 GeekSpeak.
  • http://sporm.codeplex.com/ – проект, который был найден мною буквально на днях, он так же является LINQ провайдером, даже с более “чистой” объектной моделью (вывод по поверхностному ознакомлению). Проект имеет на данный момент версию 0.1, но находится в активной разработке
LINQ to SharePoint от Microsoft

Вот мы и добрались до официальной реализации LINQ провайдера. Начнём с небольшой критики:

  • невозможность реализации Model-First

Вы помните Entity Framework 1.0? Мне хватило одного проекта, чтобы понять, что с этой версией я больше работать не буду. Model-First может быть принципиально важен при разработке проектов, в которых целевой платформой может являться не только SharePoint.

  • неполная реализация

Причиной тому архитектура хранения данных в SharePoint, в частности язык запросов, в связи с чем мы не сможем делать, к примеру запросы с JOIN, т.к. они могут нанести тяжелый удар по производительности. Есть так же набор “неэффективных запросов“. Смысл названия заключается в: 1) все те детали запроса, которые могут быть преобразованы в CAML будут преобразованы 2) будет выполнен CAML запрос 3) оставшиеся непреобразоваными части запроса будут применены к объектам в памяти, которые вернул запрос (с помощью LINQ to Objects). Другими словами вы рискуете выбрать огромное количество записей, и преобразовывать их в памяти. Большинство математических функций неэффективны. Подробный и актуальный список данных запросов можно найти по адресу: http://msdn.microsoft.com/en-us/library/ee536585(office.14).aspx

SPMetal

Генерированием модели данных для отображения занимается утилита SPMetal, её достаточно вызвать, указав расположение узла с данными, а так же указать какие именно списки и типы содержимого вы хотите отобразить.

Данный вызов, к примеру, сгенерирует файл MarketingSite, в котором будет описан так называемый DataContext, а так же все сущности из списков указанного узла.

Данная утилита обладает некоторой гибкостью в определении правил кодогенерации, о настройках можно прочитать здесь: http://msdn.microsoft.com/en-us/library/ee537010(office.14).aspx

Кстати, узлы SharePoint теперь можно просматривать через Server Explorer в Visual Studio 2010. А как мы знаем, Visual Studio 2010 довольно таки просто поддается расширению, в связи с чем SPMetal теперь можно вызывать и из графического интерфейса:

Данное расширение можно скачать здесь: http://visualstudiogallery.msdn.microsoft.com/en-us/c523e7ba-ba9d-45c4-98ea-b02b19f81640

Некоторые факты о LINQ to SharePoint

Правила преобразования типов SharePoint на CLR типы указаны в таблице по адресу: http://msdn.microsoft.com/en-us/library/ee536245(office.14).aspx

Благодаря типу EntitySet<TEntity> поддерживаются типы связей one-to-many, many-to-many, а так же ленивая загрузка связей по аналогии с Entity Framework 4.

Пример работы

Итак, мы имеем установленный SharePoint Foundation 2010, а так же Visual Studio 2010 с установленными “Imtech Get SPMetal Definition Extension”.

Создаем проект Console Application. После чего добавляем ссылку на Microsoft.SharePoint.Linq.dll. Далее, пользуясь расширениями для SPMetal генерируем контекст и сущности из списка на нашем узле в файл (в моём примере Employee.cs):

internal partial class OrganisationSiteDataContext 
    : Microsoft.SharePoint.Linq.DataContext
{}

Данный контекст пригодится нам для построения запросов. Работа с ним аналогична DataContext’у во всех технологиях типа LINQ to “Data”.

Стоит уделять особое внимание жизненному циклу данного объекта, т.к. он потребляет ресурсы, а так же должен быть уникален, к примеру для каждого пользователя, в зависимости от типа вашего приложения решения могут быть разными, можно так же воспользоваться услугами Dependency Injection контейнера.

Все сгенерированные классы будут иметь дополнительные аттрибуты, указывающие на отображение:

[ContentType(Name = »Employee»)]
public partial class Employee
{
    [Column(Name=»ID», IsId=true, ReadOnly=true, FieldType=»Counter»)]
    public int Id {getset;}
}

Кстати, эти атрибуты почему-то так же не очень гибко спроектированы, к примеру ListAttribute привязан к имени списка http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.linq.listattribute_properties(office.14).aspx, т.е. если пользователь захочет переименовать список, ваше решение хрупко пошатнется и упадёт.

Преобразованием сущностей можно управлять дополнительно, для этого реализован интерфейс ICostumMapping:

public partial class Project : ICustomMapping
{
   [CustomMapping(Columns = new String[] { «Due_x0020_Date»«IsCancelled» })]
   public void MapFrom(object listItem)
   {
        this.DueDate = ((SPListItem)listItem)[“Due_x0020_Date”];
        this.IsCancelled = ((SPListItem)listItem)[“IsCancelled”];
   }

   public void MapTo(object listItem)
   {
       ((SPListItem)listItem)[“Due_x0020_Date”] = this.DueDate;
       ((SPListItem)listItem)[“IsCancelled”] = this.IsCancelled;
   }

    public void Resolve(RefreshMode mode, object originalListItem, object databaseObject)
    {
    }
}

 

Вернемся к работе с контекстом:

using(DataContext data = new DataContext(SPContext.Current.Web.Url))
{

    EntityList<Employee> employees = data.GetList<Employee>(«Employee»);

    var youngEmployees = from employee in employees
                      where employee.Age < 25
                      select employee;
   
   //do something

}

В принципе всё аналогично LINQ to “Data”, это очень приятно, так как нет необходимости менять стиль при каждом новом решении.

Все CUD (Create, Update, Delete) операции так же поддерживаются, выглядит это примерно следующим образом:

using(DataContext data = new DataContext(SPContext.Current.Web.Url))
{

    EntityList<Employee> employees = data.GetList<Employee>(«Employee»);

    Employee newEmployee = new Employee{Name = «Вася»};    

    employees.InsertOnSubmit(newEmployee);

    data.SubmitChanges();
}

На этом я закончу данную статью, в скором времени расскажу об особенностях LINQ to SharePoint как ORM.

Материалы:

Видео по LINQ to SharePoint http://www.microsoft.com/belux/MSDN/nl/chopsticks/default.aspx?id=1517