Linq to SharePoint. Особенности. Часть 3

Часть 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

Получение списка по URL

В базовом классе Microsoft.SharePoint.Linq.DataContext, который мы используем для создание своего контекста данных, есть метод GetList. В качестве параметра этот метод принимает строковое значение, содержащее название (Title) списка. И тут же возникают две проблемы: что делать с локализацией названий списков и как быть в случае переименование списков пользователями.

Другое дело URL списка: пользователь его изменить не может и от локализации он независим. Сложность здесь заключается в том, что у SharePoint нет методов, которые бы возвращали список по его URL. На помощь нам приходит другой метод, а именно SPWeb.GetFolder(string strUrl). Вызвав этот метод, мы получим экземпляр объекта SPFolder, имеющего свойство ParentListId. Теперь сказанное в виде метода:

/// <summary>
/// Получение списка по URL
/// </summary>
private EntityList<T> GetListByUrl<T>(string listUrl)
{
    string listTitle;
    using(var site = new SPSite(Web))
    {
        using(var web = site.OpenWeb())
        {
            var folder = web.GetFolder(listUrl);
            var list = web.Lists[folder.ParentListId];
            listTitle = list.Title;
        }
    }
    return GetList<T>(listTitle);
}

Метод классный, но имеет ряд ограничений: URL должен быть указан относительно текущего узла, например:

"/Lists/Departments", 
"Lists/Departments", 
"/Lists/Departments/AllItems.aspx",
"/DocLib/Forms/AllItems.aspx"

И работать этот метод будет медленнее стандартного, т.к. создаются объекты SPSite, SPWeb, SPFolder и SPList.

Анонимный доступ

Ещё одна досадная неприятность: Linq-to-SharePoint не работает в анонимном режиме. Связано это с тем, что в случае, когда пользователь анонимен, то свойство SPContext.Current.Web.CurrentUser равно null. Из-за чего мы получаем исключение. Чтобы стало понятно, как это обойти достаточно взглянуть на конструктор класса SPServerDataConnection, используемый для получения данных:

public SPServerDataConnection(string url)
{
    if (SPContext.Current != null)
    {
        this.defaultSite = SPContext.Current.Site;
        this.defaultWeb = (SPContext.Current.Web.Url == url) 
            ? SPContext.Current.Web 
            : this.defaultSite.OpenWeb(new Uri(url).PathAndQuery);
    }
    else
    {
        this.defaultSite = new SPSite(url);
        this.defaultWeb = this.defaultSite.OpenWeb(new Uri(url).PathAndQuery);
    }
    if (!this.defaultWeb.Exists)
    {
        throw new ArgumentException(
            Resources.GetString("CannotFindWeb", new object[] { url }));
    }
    this.defaultWebUrl = this.defaultWeb.ServerRelativeUrl;
    this.openedWebs = new Dictionary<string, SPWeb>();
    this.openedWebs.Add(this.defaultWebUrl, this.defaultWeb);
}

Как видно, в случае, когда контекста нет (SPContext.Current == null), он создается заново на основе текущего URL-адреса. Это первый момент. Второй момент заключается в том, что нам нужен пользователь, чтобы выполнить операцию от его имени. В этом нам поможет метод SPSecurity.RunWithElevantPrivilages(), который исполняет переданный ему делегат от имени учетной записи, указанной в пуле веб-приложения. Вот код с описанием:

public static void RunAsAdmin(SPSecurity.CodeToRunElevated secureCode)
{
    // Анонимный доступ
    var isAnonymous = SPContext.Current.Web.CurrentUser == null;
    // Необходимо ли сбрасывать текущий контекст
    var nullUserFlag = (SPContext.Current != null && isAnonymous);
 
    if (nullUserFlag)
    {
        // Сохраняем текущий контекст
        var backupCtx = HttpContext.Current;
        // Сбрасываем текущий контекст
        HttpContext.Current = null;
        // Исполняем код
        SPSecurity.RunWithElevatedPrivileges(secureCode);
        // Возвращаем контекст к предыдущему значению
        HttpContext.Current = backupCtx;
    }
    else
    {
        // Исполняем как есть
        SPSecurity.RunWithElevatedPrivileges(secureCode);
    }
}

Этот статический метод можно вставить в ваш класс, реализующий контекст.

Cross-Site запросы данных в Linq-to-SharePoint

Суть cross-site запросов заключается в том, что контекст созданный для одного сайта может обращаться к данным из другого сайта, но только в пределах одной сайт-коллекции. Возвращаясь к демонстрационному проекту из 2 части, приведу следующий пример: есть два уза с двумя списками, структура которых идентична. Чтобы дать понять контексту, что теперь данные для списка надо брать из другого узла, надо вызвать метод RegisterList(), передав ему в качестве параметров название нового списка, относительный URL нового узла:

using(var ctx = new ZhukBlogContext("http://spserver"))
{
    var rootEmployees = ctx.Employees.ToList();
    ctx.RegisterList<Employees>("Employees", "/SubSite", "Employees");
    var subEmployees = ctx.Employees.ToList();
}

Вот как-то так.

Теги

Поделиться

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

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

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