【问题标题】:Passing lambda expression to LINQ Include() (SharePoint CSOM)将 lambda 表达式传递给 LINQ Include() (SharePoint CSOM)
【发布时间】:2014-01-20 22:38:11
【问题描述】:

我尝试过搜索,但似乎没有找到任何相关的答案。也许是因为我不确定如何提出我的问题。

我正在编写一个类库来帮助使用 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&lt;ListItem&gt; 而不是ListItemCollection 并且我想传递Expression&lt;Func&lt;ListItem, object&gt;&gt; 类型的参数而不是Expression&lt;Func&lt;ListItemCollection, object&gt;&gt;...根本不向用户介绍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));

但是,这会在调用 Load() 方法时抛出 OperationNotSupportedException

如有任何指导,我将不胜感激。谢谢!

【问题讨论】:

  • 异常信息是什么?
  • 我相信它是空的......或者通常没有帮助。
  • 你不想items =&gt; items.Select(x =&gt; new { x.ID, x.DisplayName })。我没有仔细阅读您的问题,但看起来您正在尝试使用 Include 进行投影,而 Select 正是为此目的而制作的。
  • @evanmcdonnal 例如,通过使用匿名对象而不是实际的 ListItem,您无法更新项目。
  • 使用您提供的后一个代码,它按我的预期工作;它似乎没有任何问题。

标签: c# linq sharepoint lambda parameter-passing


【解决方案1】:

直接在查询本身上调用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&lt;Func&lt;ListItemCollection, ItemSelector, object&gt;&gt;,它将接受一个集合、一个选择器,并将其映射到一个对象。这里ItemSelector 是通过using ItemSelector = Expression&lt;Func&lt;ListItem, object&gt;&gt;; 定义的(因为试图使用Expression&lt;Func&lt;ListItemCollection, Expression&lt;Func&lt;ListItem, object&gt;&gt;, object&gt;&gt; 只是残忍和不寻常的惩罚)。我们可以这样定义:

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&lt;Func&lt;ListItemCollection, object&gt;&gt;[],这正是我们需要传递给 Load 的内容。

哇。这是实际执行此操作的最终代码:

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;
}

【讨论】:

  • 谢谢,这会奏效。我没有提到我还使用ListItemCollectionListItemCollectionPosition 属性并在查询中指定RowLimit 以实现“分页”以一次传输更少的数据。使用 LoadQuery() 时,我不再有可用的 ListItemCollectionPosition(我没有得到 ListItemCollection)。不过,好点子。
  • @nixx 查看适合您的解决方案的编辑。请注意,它看起来比实际更令人生畏;您只需要能够将其分解为每个小部分,单独分析并不太难。
  • 非常感谢!这是一些进步,但不幸的是,我仍然得到“不支持查询表达式”异常。我比较了直接传递的表达式和通过调试器中的参数传递的表达式,尽管它们看起来相同,但它们不是。我在这里发布了比较:pastebin.com/fhq3YReN
  • 另一个区别是如何处理多个选择器。第一个(顶部)示例是直接示例(工作),另一个是使用 Apply() 方法传递的示例:imgur.com/a/hctZh
  • 什么是“ItemSelector”?这个类在我的 .NET 核心控制台项目中没有得到解决。还有@nixx,有用吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多