2014-01-20 2 views
2

Я пробовал поиск, но, похоже, я не нашел подходящих ответов. Возможно, потому, что я не совсем уверен, как сформулировать мой вопрос.Передача выражения лямбда LINQ Include() (SharePoint CSOM)

Я пишу библиотеку классов, чтобы помочь работать с объектной моделью на стороне клиента SharePoint. При выполнении запроса можно указать, какие свойства возвращаемых объектов должны быть загружены, чтобы избежать ненужного сетевого трафика. Это делается с помощью выражения Lambda.

Вот пример, который работает:

public ListItemCollection GetItems(
       params Expression<Func<ListItemCollection, object>>[] retrievals) 
{ 
    var query = new CamlQuery {...}; 
    ListItemCollection queryResults = _list.GetItems(query); 
    ReloadClientObject(queryResults, retrievals) 
    return queryResults; 
} 

public void ReloadClientObject<T>(T clientObject, 
      params Expression<Func<T, object>>[] retrievals) 
      where T : ClientObject 
{ 
    _context.Load(clientObject, retrievals); 
    _context.ExecuteQuery(); 
} 

Пример вызова:

var items = GetItems(items => items.Include(
             item => item.Id, 
             item => item.DisplayName)); 

Это все будет в порядке. Но я предпочел бы вернуть IEnumerable<ListItem> вместо ListItemCollection и хотел бы передать параметры типа Expression<Func<ListItem, object>> вместо Expression<Func<ListItemCollection, object>> ... не вводить пользователя в ListItemCollection. Итак, я хотел бы переместить вызов Include() на тело моего метода ... и вот где я застрял.

Вот что у меня до сих пор:

public IEnumerable<ListItem> GetItems(
          params Expression<Func<ListItem, object>>[] retrievals) 
{ 
    var query = new CamlQuery {...}; 
    ListItemCollection queryResults = _list.GetItems(query); 
    ReloadClientObject(queryResults, items => items.Include(retrievals)) 
    _context.ExecuteQuery(); 
    return queryResults.AsEnumerable(); 
} 

Пример вызова (намного чище и приятнее):

var items = GetItems(item => item.Id, item => item.DisplayName)); 

Однако, это бросает OperationNotSupportedException при вызове метода Load().

Я был бы признателен за любые рекомендации. Спасибо!

+0

Какое сообщение об исключении? – i3arnon

+0

Я считаю, что это было пусто ... или вообще не полезно. – nixx

+0

Вам не нужны элементы item items. Выберите (x => new {x.ID, x.DisplayName}) '. Я не внимательно изучил ваш вопрос, но похоже, что вы пытаетесь использовать 'Include' для выполнения проекции, когда' Select' создан для этой цели. – evanmcdonnal

ответ

3

Вызов Include непосредственно на сам запрос, а затем просто использовать LoadQuery вместо Load, чтобы загрузить запрос:

public IEnumerable<ListItem> GetItems(this ClientContext context, 
    string listName, 
    params Expression<Func<ListItem, object>>[] retrievals) 
{ 
    var query = new CamlQuery(); 

    var queryResults = context.Web.Lists.GetByTitle(listName) 
     .GetItems(query) 
     .Include(retrievals); 
    context.LoadQuery(queryResults); 
    context.ExecuteQuery(); 
    return queryResults; 
} 

Так как это не работает для вас (в соответствии с your comment о том, что вам нужно использовать функциональность пейджинга), нам нужно будет немного поработать.

Итак, что мы сделаем здесь, создайте Expression<Func<ListItemCollection, ItemSelector, object>>, который возьмет коллекцию, селектор и сопоставляет это объекту. Здесь ItemSelector определяется через using ItemSelector = Expression<Func<ListItem, object>>; (потому что попытка использовать Expression<Func<ListItemCollection, Expression<Func<ListItem, object>>, object>> - это просто жестокое и необычное наказание). Мы можем определить его следующим образом:

Expression<Func<ListItemCollection, ItemSelector, object>> includeSelector = 
    (items, selector) => items.Include(selector); 

Теперь то, что мы можем сделать, это написать Apply метод, который может принимать выражение функции принимает два параметра, заменить все экземпляры второго параметра с константой, и, таким образом, создать метод с одним меньшим параметром.Вот определение этого Apply метода:

public static Expression<Func<T1, TResult>> Apply<T1, T2, TResult>(
    this Expression<Func<T1, T2, TResult>> expression, 
    T2 value) 
{ 
    return Expression.Lambda<Func<T1, TResult>>(
     expression.Body.Replace(expression.Parameters[1], 
      Expression.Constant(value)) 
     , expression.Parameters[0]); 
} 

Это использует этот вспомогательный метод для замены всех экземпляров одного выражения с другим:

public static Expression Replace(this Expression expression, 
    Expression searchEx, Expression replaceEx) 
{ 
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression); 
} 

internal class ReplaceVisitor : ExpressionVisitor 
{ 
    private readonly Expression from, to; 
    public ReplaceVisitor(Expression from, Expression to) 
    { 
     this.from = from; 
     this.to = to; 
    } 
    public override Expression Visit(Expression node) 
    { 
     return node == from ? to : base.Visit(node); 
    } 
} 

Итак, теперь мы можем принять это includeSelector выражения и для каждого селектор элементов в нашем массиве, примените этот селектор к этой функции. Принимая эти результаты и помещая их в массив, мы получаем Expression<Func<ListItemCollection, object>>[], и это именно то, что нам нужно передать до Load.

Whew. Вот окончательный код на самом деле:

public static IEnumerable<ListItem> GetItems(this ClientContext context, 
    string listName, 
    params Expression<Func<ListItem, object>>[] retrievals) 
{ 
    var query = new CamlQuery(); 

    var queryResults = context.Web.Lists.GetByTitle(listName) 
     .GetItems(query); 

    Expression<Func<ListItemCollection, ItemSelector, object>> includeSelector = 
     (items, selector) => items.Include(selector); 

    context.Load(queryResults, retrievals 
     .Select(selector => includeSelector.Apply(selector)) 
     .ToArray()); 
    context.ExecuteQuery(); 
    return queryResults; 
} 
+0

Спасибо, это сработает. Я не упомянул, что я также использую свойство ListItemCollectionPosition для «ListItemCollection» и указываю RowLimit в запросе, чтобы реализовать «пейджинг» для передачи меньше данных одновременно. При использовании LoadQuery() у меня больше нет свойства ListItemCollectionPosition (я не получаю 'ListItemCollection'). Хороший момент. – nixx

+0

@nixx См. Редактирование решения, которое будет работать для вас. Обратите внимание, что это выглядит несколько более устрашающе, чем на самом деле; вам просто нужно разбить его на каждую маленькую кусочек, что не слишком сложно анализировать изолированно. – Servy

+0

Большое спасибо! Это какой-то прогресс, но, к сожалению, я все еще получаю исключение «Неверное выражение запроса». Я сравнил выражение, переданное напрямую, и через параметр в отладчике, и хотя они кажутся одинаковыми, это не так. Я опубликовал сравнение здесь: http://pastebin.com/fhq3YReN – nixx

Смежные вопросы