【问题标题】:Faster way to get distinct values in LINQ?在 LINQ 中获得不同值的更快方法?
【发布时间】:2016-12-01 23:01:26
【问题描述】:

我在 SharePoint 中有一个 Web 部件,我正在尝试使用列表中特定字段的唯一/不同值填充下拉控件。

不幸的是,由于系统的性质,它是一个文本字段,因此没有其他确定的来源来获取数据值(即,如果它是一个选择字段,我可以获得字段定义并得到那里的值),并且我在后续的 CAML 查询中使用下拉列表的选定值,因此值 必须 准确到列表项上的内容。目前该列表有 arpprox。 4K 项目,但它正在(并将继续)缓慢增长。

而且,它是沙盒解决方案的一部分,因此受到用户代码服务时间限制的限制——而且它经常超时。在我的开发环境中,我在调试中逐步完成了代码,看起来我实际获得不同值的 LINQ 行是最耗时的,然后我完全注释掉了对该方法的调用,超时停止,所以我相当肯定这就是问题所在。

这是我的代码:

private void AddUniqueValues(SPList list, SPField filterField, DropDownList dropDownControl)
{
    SPQuery query = new SPQuery();
    query.ViewFields = string.Format("<FieldRef Name='{0}' />", filterField.InternalName);
    query.ViewFieldsOnly = true;

    SPListItemCollection results = list.GetItems(query); // retrieves ~4K items 

    List<string> uniqueValues = results.Cast<SPListItem>().Select(item => item[filterField.Id].ToString()).Distinct().ToList(); // this takes too long with 4K items 

    uniqueValues.Sort();

    dropDownControl.Items.AddRange(uniqueValues.Select(itm => new ListItem(itm)).ToArray());
}

据我所知,没有办法直接在 CAML 查询中获取“不同”值,那么我怎样才能更快地做到这一点?有没有办法重组 LINQ 以更快地运行?

是否有一种简单/快速的方法可以从客户端执行此操作? (最好使用 REST,但如有必要,我会使用 JSOM)。


我想在这里添加一些额外的信息,因为我做了一些进一步的测试并发现了一些有趣的结果。

首先,解决Cast()Select() 是否需要的问题:是的,需要。

SPListItemCollectionIEnumerable 但不是 IEnumerable&lt;T&gt;,所以我们需要强制转换才能完全使用 LINQ。

然后在它被转换为IEnumerable&lt;SPListItem&gt; 之后,SPListItem 是一个相当复杂的对象,我希望从该对象的 one 属性中找到不同的值。直接在IEnumerable&lt;SPListItem&gt; 上使用Distinct() 会产生......所有这些。所以我必须Select() 只是我想比较的单个值。

所以是的,Cast()Select() 是绝对必要的。

正如 M.kazem Akhgary 在 cmets 中所述,在我的原始代码行中,每次调用 ToString()(对于 4K 项目)确实增加了一些时间。但在测试其他一些变体时:

// original
List<string> uniqueValues = results.Cast<SPListItem>().Select(item => item[filterField.Id].ToString()).Distinct().ToList();

// hash set alternative
HashSet<object> items = new HashSet<object>(results.Cast<SPListItem>().Select(itm => itm[filterField.Id]));

// don't call ToString(), just deal with base objects
List<object> obs = results.Cast<SPListItem>().Select(itm => itm[filterField.Id]).Distinct().ToList();

// alternate LINQ syntax from Pieter_Daems answer, seems to remove the Cast()
var things = (from SPListItem item in results select item[filterField.Id]).Distinct().ToList();

我发现所有这些方法都需要几十秒才能完成。奇怪的是,Pieter_Daems answer 中的 DataTable/DataView 方法,我在其中添加了一点以提取我想要的值:

DataTable dt = results2.GetDataTable();
DataView vw = new DataView(dt);
DataTable udt = vw.ToTable(true, filterField.InternalName);
List<string> rowValues = new List<string>();
foreach (DataRow row in udt.Rows)
{
    rowValues.Add(row[filterField.InternalName].ToString());
}
rowValues.Sort();

只花了 1-2 秒

最后,我选择Thriggle's answer,因为它很好地处理了 SharePoint 的 5000 项列表视图阈值,我可能有一天会处理它,而且它只比它慢一点(2-3 秒) DataTable 方法。仍然比所有 LINQ 快得多。

不过,有趣的是,从 SPListItemCollection 获取特定字段的不同值的最快方法似乎是 DataTable/DataView 转换方法。

【问题讨论】:

  • 我认为那是因为item[filterField.Id].ToString() 部分。 ToString 方法是否被覆盖?如果不是,那么它基本上一遍又一遍地返回相同的字符串,你不会从散列中受益
  • 不能将 Distinct 添加到检索中吗? SPListItemCollection results = list.GetItems(query).Distinct()?
  • .Cast.Select.Unique() linq 需要执行多长时间?

标签: c# performance linq sharepoint


【解决方案1】:

在检查独特性之前先检索所有项目,可能会导致显着延迟。

另一种方法是针对 SharePoint 执行多个 CAML 查询;这将导致每个唯一值一个查询(加上一个不返回结果的最终查询)。

  1. 确保您的列表已将列索引应用于要枚举其值的字段。
  2. 在您的初始 CAML 查询中,按要枚举的字段排序并施加行限制为一个项。
  3. 从该查询返回的项目中获取字段的值,并将其添加到您的唯一值集合中。
  4. 再次查询列表,按字段排序并将行数限制为 1,但这次添加 过滤器 条件,使其仅检索字段值大于字段值的项目您刚刚检测到。
  5. 将返回项目中的字段值添加到您的唯一值集合中。
  6. 重复第 4 步和第 5 步,直到查询返回一个空结果集,此时您的唯一值集合应包含该字段的所有当前值(假设自您开始以来尚未添加更多值)。

这会更快吗?这取决于您的数据,以及重复值出现的频率。

如果您有 4000 个项目且只有 5 个唯一值,那么您将能够在 6 个轻量级 CAML 查询中收集这 5 个值,总共返回 5 个项目。这比查询所有 4000 项并一次枚举一项以查找唯一值更有意义。

另一方面,如果您有 4000 个项目和 3000 个唯一值,那么您将查询列表 3001 次。这可能比在单个查询中检索所有项目并使用后处理来查找唯一值要慢。

【讨论】:

  • 有趣的方法,我会尝试一下。 FWIW 目前有 28 个唯一值,并且添加唯一值的速度将远低于整体项目增加的速度,所以我认为这可能是要走的路。
  • 如果您使用的是 SharePoint 2010 或 2013,此方法也很有用,因此会受到列表视图阈值的限制,该阈值会限制任何返回 5000 个或更多项目的查询。在这种情况下,列索引尤其重要。
  • 是的,请记住 5K 列表视图阈值,我将使用此技术,谢谢!它比我尝试的 LINQ 方法快得多,但有趣的是不是最快的……(如果您有兴趣,请参阅我更新的问题)。
【解决方案2】:
var distinctItems = (from SPListItem item in items select item["EmployeeName"]).Distinct().ToArray();

或将您的结果转换为 DataView 并执行以下操作:

SPList oList = SPContext.Current.Web.Lists["ListName"];
SPQuery query = new SPQuery();
query.Query = "<OrderBy><FieldRef Name='Name' /></OrderBy>";
DataTable dtcamltest = oList.GetItems(query).GetDataTable();
DataView dtview = new DataView(dtcamltest);
DataTable dtdistinct = dtview.ToTable(true, "Name");

来源:https://sharepoint.stackexchange.com/questions/77988/caml-query-on-sharepoint-list-without-duplicates

【讨论】:

  • @Pieter_Diems 最终我会选择另一个答案,但有趣的是DataTable / DataView 转换方法是最快的。如果您有兴趣,请查看我更新的问题。
【解决方案3】:

Duplicate maybe?

.Distinct 是一个 O(n) 调用。 没有比这更快的了。

话虽如此,也许您想检查是否需要强制转换 + 选择以获得唯一性 - 我会尝试 HashSet。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-08
    相关资源
    最近更新 更多