Azure DevOps 的 OData Analytics 查询指南

Azure DevOps Services |Azure DevOps Server |Azure DevOps Server 2022

扩展开发人员可以遵循本文中提供的准则,针对 Azure DevOps 的 Analytics 设计高效的 OData 查询,从而受益。 遵循这些准则有助于确保查询具有良好的执行时间和资源消耗性能。 不符合这些准则的查询可能会导致性能不佳,报表等待时间长、超出允许的资源消耗的查询或服务阻塞。

注意

Azure DevOps Services 中所有服务的生产中会自动启用并支持 Analytics 服务。 Power BI 集成和对 Analytics 服务 OData 源的访问已正式发布。 建议你使用 Analytics OData 源并提供反馈。

可用数据依赖于版本。 OData API 的最新支持版本是 v2.0,最新的预览版本是 v4.0-preview。 有关详细信息,请参阅 OData API 版本控制

注意

Azure DevOps Server 2020 及更高版本的所有新项目集合都会自动安装并在生产环境中支持 Analytics 服务。 Power BI 集成和对 Analytics 服务 OData 源的访问已正式发布。 建议你使用 Analytics OData 源并提供反馈。 如果从 Azure DevOps Server 2019 升级,可以在升级期间安装 Analytics 服务。

可用数据依赖于版本。 OData API 的最新支持版本是 v2.0,最新的预览版本是 v4.0-preview。 有关详细信息,请参阅 OData API 版本控制

这些准则是我们以“DO”、“CONSIDER”、“AVOID”和“DON'T”为前缀的建议。 Analytics 强制实施的限制性规则包含 [BLOCKED] 前缀。 你应该了解不同解决方案之间的权衡。 在某些情况下,您可能会因数据要求而不得不违反一项或多项准则。 此类情况应很少见。 我们建议你对此类决策有明确而令人信服的理由。

提示

本文档中显示的示例基于 Azure DevOps Services URL。 对于本地部署版本,使用替代。

https://{servername}:{port}/tfs/{OrganizationName}/{ProjectName}/_odata/{version}/

错误和警告消息

✔️ 务必检查 OData 响应警告

您执行的每个查询都会根据一组预定义的规则进行检查。 违规会在 @vsts.warnings 后面返回 OData 响应。 查看这些警告,因为它们提供有关如何改进查询的当前和上下文敏感信息。

{
  "@odata.context": "https://{OrganizationName}.tfsallin.net/_odata/v1.0/$metadata#WorkItems",
  "@vsts.warnings": [
    "The specified query does not include a $select or $apply clause which is recommended for all queries."
  ],
  ...
}

✔️ 务必查看 OData 错误消息

违反 OData 错误规则的查询会导致响应失败,并出现 400(请求错误)状态代码。 关联消息不会显示在@vsts.warnings属性中。 相反,它们会在 JSON 响应的 message 属性中生成错误消息。

{
  "error": {
  "code": "0",
  "message": "The query specified in the URI is not valid. The Snapshot tables in Analytics are intended to be used only in an aggregation."
  }
}

限制

应做事项

考虑

被阻止

避免

✔️ 将查询限制为有权访问的项目

如果查询以您无权访问的项目中的数据为目标,查询将返回“项目访问被拒绝”消息。 若要确保你有权访问,请确保 视图分析 权限设置为“允许查询的所有项目”。 有关详细信息,请参阅 访问 Analytics 所需的权限。

如果无权访问项目,将显示以下消息:

查询结果包括一个或多个您无权访问的项目中的数据。 添加一个或多个项目筛选器以指定您在“WorkItems”实体中有权访问的项目。 如果你在使用$expand或导航属性,则为这些实体提供项目过滤器是必须的。

若要解决此问题,可以显式添加项目筛选器,也可以使用 项目范围的终结点 ,如本文稍后所述。

例如,以下查询提取属于名为{projectSK1}{projectSK2}的项目的工作项。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=ProjectSK eq {projectSK1} or ProjectSK eq {projectSK2}
  &$select=WorkItemId, Title

✔️ 如果扩展可能包含其他可能无法访问的项目中的数据,请在 $expand 子句中指定项目筛选器。

展开导航属性时,最终有可能引用其他不可访问的项目中的数据。 如果引用不可访问的数据,将收到前面列出的相同错误消息“ 查询结果包括一个或多个项目中的数据...”。 同样,可以通过添加显式项目筛选器来控制扩展的数据来解决此问题。

可以在常规 $filter 子句中为简单导航属性执行此操作。 例如,以下查询显式请求 WorkItemLinks 链接及其目标存在于同一项目中的位置。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemLinks?
  $filter=ProjectSK eq {projectSK} and TargetWorkItem/ProjectSK eq {projectSK}
  &$select=LinkTypeReferenceName, SourceWorkItemId, TargetWorkItemId
  &$expand=TargetWorkItem($select=WorkItemId, Title)

相反,你可以将筛选器移到 $expand 子句中的 $filter 扩展选项。 但是,它会更改查询的语义。 例如,以下查询从给定项目获取所有链接,并且仅在目标存在于同一项目中时才有条件地展开目标。 虽然有效,但这种方法可能会导致混淆,因为可能难以确定属性是否未展开是因为它是 null 还是因为它被筛选掉了。仅当你确实需要此特定行为时才使用此解决方案。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemLinks?
  $filter=ProjectSK eq {projectSK}
  &$select=LinkTypeReferenceName, SourceWorkItemId, TargetWorkItemId
  &$expand=TargetWorkItem($filter=ProjectSK eq {projectSK}; $select=WorkItemId, Title)

使用 expand 集合属性(如 WorkItems 实体集中的 Children)时,$filter 扩展选项非常有用。 例如,以下查询返回给定项目中的所有工作项及其属于同一项目的所有子项。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=ProjectSK eq {projectSK}
  &$select=WorkItemId, Title
  &$expand=Children($filter=ProjectSK eq {projectSK}; $select=WorkItemId, Title)

如果展开以下属性之一,请指定筛选器:

  • WorkItems实体集:ParentChildren
  • WorkItemLinks 实体集: TargetWorkItem.

✔️ 考虑使用项目范围内的终结点进行查询

如果你对单个项目的数据感兴趣,我们建议你使用项目范围的 OData 终结点 (/{ProjectName}/_odata/v1.0)。 它避免了前面两节中所述的问题,并将数据隐式筛选到一个项目、引用的实体集和所有扩展的导航属性。

通过这种简化,可以将上一部分中的查询重写为以下形式。 expand 子句中的筛选器不仅会消失,而且不需要主实体集上的筛选器。

https://analytics.dev.azure.com/{OrganizationName}/{ProjectName}/_odata/{version}//WorkItemLinks?
  &$select=LinkTypeReferenceName, SourceWorkItemId, TargetWorkItemId
  &$expand=TargetWorkItem($select=WorkItemId, Title)

工作项子项的查询也更短、更简单。

https://analytics.dev.azure.com/{OrganizationName}/{ProjectName}/_odata/{version}//WorkItems?
  &$select=WorkItemId, Title
  &$expand=Children($select=WorkItemId, Title)

仅当焦点来自单个项目的数据时,才能应用此解决方案。 对于跨项目报告,必须使用前面部分所述的筛选策略。

✔️ 如果查询超出使用限制,请等待或停止操作

如果执行许多查询,或者查询需要运行许多资源,则可能超出服务限制并暂时被阻止。 如果超出服务限制,请停止操作,因为你发送的下一个查询会失败并显示相同的错误消息。

由于超出命名空间“{namespace}”中的资源“{resource}”的使用,请求被阻止。

有关速率限制的详细信息,请参阅 速率限制。 若要了解如何设计高效的 OData 查询,请参阅 本文后面的性能准则

✔️ 如果查询失败且超时,请等待或停止操作

与超出使用限制类似,如果查询遇到超时,应等待或停止操作。 它可以发出暂时性问题信号,因此可以重试一次,看看问题是否得到解决。 但是,持续超时表示查询可能需要过多资源,因此导致无法运行。 进一步重试只会导致超出使用限制,并被阻止。

TF400733:请求已取消:请求已超过请求超时,请重试。

超时表示查询需要优化。 若要了解如何设计高效的 OData 查询,请参阅 本文后面的性能指南

❌ [已阻止] 除用于聚合外,不要将快照实体用于任何其他内容

带后缀的 Snapshot 快照实体集很特殊,因为它们被建模为 每日快照。 你可以使用它们获取实体在过去每一天结束时的状态。 例如,如果查询 WorkItemSnapshot 并筛选为单个 WorkItemId记录,则创建工作项后,每天会收到一条记录。 直接加载所有这些数据的成本很高,并且很可能超过使用限制并被阻止。 但是,允许并推荐对这些实体进行聚合操作。 事实上,快照实体集在设计时考虑到了聚合场景。

例如,以下查询按日期获取工作项数量,以观察其在 2020 年 1 月期间的增涨情况。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemSnapshot?
  $apply=
    filter(DateSK ge 20200101 and DateSK le 20200131)/
    groupby((DateSK), aggregate($count as Count))

有关聚合的详细信息,请参阅 聚合数据

✔️ 在对快照表进行聚合时,请务必在 groupby 子句中包含 DateSKDateValue

由于所有快照实体都建模为 每日快照表,因此应始终在分组子句中包含其中一个日期属性(DateSKDateValue)。 否则,结果可能会出现不正确的膨胀。

例如,如果 WorkItemSnapshot 仅按 AssignedTo 属性分组并使用计数对其进行聚合,则分配给人员的所有工作项数将乘以每个工作分配处于活动状态的天数。 尽管可能会出现你期望的结果,但这样的情况非常少见。

❌ [已阻止]请勿在资源路径中使用实体键进行实体寻址

OData 语法提供了一种通过直接在 URL 段中包含特定实体的键来访问特定实体的方法。 有关详细信息,请参阅 OData 版本 4.0。第 2 部分:URL 约定 - 4.3 寻址实体。 尽管 OData 允许此类寻址,但 Analytics 会阻止它。 在查询中包含会导致以下错误。

URI 中指定的查询无效。 Analytics 不支持键或属性导航,如 WorkItems(Id)或 WorkItem(Id)/AssignedTo。 如果在 PowerBI 中收到该错误,请重写查询以避免导致 N+1 问题的错误折叠。

正如错误消息所指示的,某些客户端工具可能会滥用直接实体寻址。 此类客户端可以选择单独查询每个实体,而不是在单个请求中加载所有数据。 不建议这样做,因为它可能会导致大量请求。 相反,我们建议使用显式实体寻址,如以下部分所述。

✔️ 务必明确使用筛选子句来处理实体

如果要提取单个实体的数据,应使用与实体集合相同的方法,并在子句中 $filter 显式定义筛选器。

例如,以下查询按其标识符获取单个工作项。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=WorkItemId eq {id}
  &$select=WorkItemId, Title

如果不确定应包含在此类筛选器中的哪些属性,可以在元数据中查找它。 请参阅 为 Analytics 构造 OData 查询、用于查询元数据的 URL 组件。 属性位于 EntityTypeKey 元素中。 例如,WorkItemIdRevisionWorkItemRevision 实体的关键列。

<EntityType Name="WorkItemRevision">
  <Key>
    <PropertyRef Name="WorkItemId"/>
    <PropertyRef Name="Revision"/>
  </Key>
  [...]
</EntityType>

❌ [已阻止] 不要对 WorkItem 实体扩展 Revisions

Analytics 数据模型不允许某些类型的扩展。 可能令人吃惊的其中一项是 WorkItem 实体上的 Revisions 集合属性。 如果尝试展开此属性,将收到以下错误消息。

URI 中指定的查询无效。 属性“Revisions”不能用于$expand查询选项。

已实施此限制,旨在鼓励每个人使用推荐的解决方案,即从WorkItemRevisions提取修订,如下节所述。

✔️ 务必使用 WorkItemRevisions 实体集来加载给定工作项的所有修订

每次要获取工作项或工作项集合的完整历史记录时,请使用 WorkItemRevisions

例如,以下查询返回具有 {id} 标识符的工作项的所有修订。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemRevisions?
  $filter=WorkItemId eq {id}
  &$select=WorkItemId, Title

如果关心符合特定条件的所有工作项的完整历史记录,请使用导航属性上的 WorkItem 筛选器来表达它。 例如,以下查询可以获取所有当前处于活动状态的工作项的所有修订。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemRevisions?
  $filter=WorkItem/State eq 'Active'
  &$select=WorkItemId, Title

❌ [已阻止]不要对不同列进行分组

使用分组操作减少记录数。 在 groupby 子句中使用不同的列表示出现问题,查询会立即失败。 如果意外遇到这种情况,将收到以下错误消息。

不推荐使用在此查询的 groupby 子句中指定的一个或多个列。

若要解决此问题,请从groupby 子句中删除唯一列。

❌ [已阻止]请勿使用 countdistinct 聚合

分析不支持该 countdistinct 函数,虽然 OData 支持。 虽然我们计划在未来添加支持,但目前尚不可用。 包含此函数的查询返回以下错误消息。

不支持在聚合中应用去重计数的查询。

❌ 避免可能导致算术溢出的聚合

在极少数情况下,聚合查询可能会遇到算术溢出问题。 例如,在对某些不用于求和的数字属性(例如 StackRank 工作项实体中)求和时,可能会发生这种情况。 由于用于数据聚合标准的 OData 扩展不提供将属性强制转换为其他类型的方法,因此解决此问题的唯一方法是从聚合中删除有问题的属性。

✔️ 务必对较长的查询使用批处理终结点

长时间查询可能会导致问题。 具体而言,在以下情况下可能会出现问题:

  • 你查询了一个具有许多自定义字段的项目。
  • 查询以编程方式构造。

发送 HTTP GET 的 OData 查询的当前限制为 3,000 个字符。 如果超过它,则返回“404 未找到”响应。

HTTP/1.1 404 Not Found
Content-Length: 0

若要解决此问题,请使用规范 OData 版本 4.0 中所述的 OData 批处理终结点。第 1 部分:协议 - 11.7 批处理请求。 Batch 功能主要用于将多个操作分组到单个 HTTP 请求有效负载中,但也可以将其用作查询长度限制的解决方法。 通过发送 HTTP POST 请求,可以传递任意长度的查询,服务可以正确解释它。

❌ [已阻止]不要使用批处理终结点发送多个查询

我们限制使用批处理终结点来处理一批多个请求。 单个请求仍只能有一个查询。 如果尝试发送一批多个查询,操作将失败并显示以下错误消息。 唯一的解决方案是将查询拆分为多个请求。

分析不支持处理当前批处理消息包含的多个操作。 分析使用 OData 批处理来支持 POST 请求,但要求将操作限制为单个请求。

❌ [已阻止]不要使用导致超过 800 列的查询

我们限制导致超过 800 列的查询。 如果查询返回的列不够选择性,可能会收到以下错误消息。

VS403670:指定的查询返回的列数为“N”,高于允许的 800 列限制。 请使用显式$select(包括$expand)选项来限制列数。

将 $select 子句添加到查询中,添加到查询内的 $expand 操作中,以避免超过此限制。

❌ 避免创建长查询

我们建议在构造较长的查询时评估你的方法。 虽然有许多场景需要长查询(例如复杂的筛选器或属性的长列表),但它们通常是次优设计的早期指示器。

当您的查询包含许多实体键时(例如,WorkItemId eq {id 1} or WorkItemId eq {id 2} or ...),很可能可以对其进行重写。 尝试定义一些选择相同实体集的其他条件,而不是传递标识符。 有时可能需要修改过程(例如,添加新字段或标记),但通常值得。 使用更多抽象筛选器的查询更易于维护,并且具有更好的工作潜力。

当包含多个单独的日期(例如 DateSK eq {dateSK 1} or DateSK eq {dateSK 2} or ...)时,会出现另一种倾向于生成较长查询的情况。 查找可用于创建更抽象筛选器的另一种模式。 例如,以下查询返回在星期一创建的所有工作项。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedOn/DayOfWeek eq 2
  &$select=WorkItemId, Title, State

✔️ 在对日期列进行筛选时,请指定时区

时区 (Edm.DateTimeOffset) 公开所有日期和时间信息,其偏移量与组织的时区设置匹配。 此数据是精确且易于同时解释的。 另一个不显眼的后果是,所有筛选器也必须传递时区信息。 如果跳过它,将收到以下错误消息。

URI 中指定的查询无效。 未指定日期/时间偏移量。 使用以下任一格式 YYYY-MM-ddZ 指定自午夜以来的所有内容或 yyyy-MM-ddThh:mm-hh:mm (ISO 8601 标准日期和时间表示形式)来指定偏移量。

若要解决此问题,请添加时区信息。 例如,假设组织配置为在“(UTC-08:00)太平洋时间(美国和加拿大)”时区显示数据,以下查询将获取自 2020 年初开始创建的所有工作项。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDate ge 2020-01-01T00:00:00-08:00
  &$select=WorkItemId, Title, State

同一解决方案适用于具有正偏移量的时区,但是加号字符 (+) 在 URI 中具有特殊含义,并且必须正确处理。 如果指定 2020-01-01T00:00:00+08:00 (使用 + 字符)作为起点,则会出现以下错误。

URI 中指定的查询无效。 “CreatedDate ge 2020-01-01T0000 08:00”中位置 31 处的语法错误。

若要解决此问题,请将 + 字符替换为其编码的版本 %2B。 例如,假设组织配置为在“(UTC+08:00)北京、重庆、香港、乌鲁木齐”时区显示数据,以下查询将返回自 2020 年初创建的所有工作项。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDate ge 2020-01-01T00:00:00%2B08:00
  &$select=WorkItemId, Title, State

另一种方法是使用日期代理键属性,因为它们不保留时区信息。 例如,以下查询返回自 2020 年初开始创建的所有工作项,而不考虑组织的设置。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDateSK ge 20200101
  &$select=WorkItemId, Title, State

性能准则

可执行的操作

请勿

考虑

避免

✔️ DO 衡量性能准则的实施效果

与任何性能建议一样,不应盲目实现它们。 相反,您应始终捕获基线并 评估 您所做更改的效果。 所有准则都基于与具有特定要求和挑战的分析客户端的交互而创建。 这些建议被视为常规建议,对于设计类似查询的任何人员都可能很有用。 但是,在极少数情况下,遵循准则可能不会对性能产生影响,甚至产生负面影响。 你确实需要测量差异才能注意到它。 如果发生这种情况,请开发者社区门户中提供反馈。

有许多选项可用于度量性能。 最简单的方法是在浏览器中直接运行同一查询的两个版本。 观察开发人员工具中所花费的时间。 例如,可以在 Microsoft Edge F12 开发人员工具中使用网络面板。 另一个选项是使用 Fiddler Web 调试器工具捕获此信息。

无论采用哪种方法,都多次运行这两个查询。 例如,运行每个查询 30 次,以设置足够大的样本集。 然后找出性能特征。 分析遵循多租户体系结构。 因此,同时发生的其他操作可能会影响查询的持续时间。

✔️ 使用聚合扩展

到目前为止,为了提高查询的性能,最好是使用聚合扩展 - 用于数据聚合的 OData 扩展。 使用聚合扩展时,您可以请求服务在服务器端汇总数据,并返回的响应比在客户端应用相同函数获取的响应更小。 最后,Analytics 已针对此类查询进行优化,因此请使用它。

有关详细信息,请参阅 聚合数据

✔️ 务必在 $select 子句中指定列

$select 子句中指定你关注的列。 分析基于 列存储索引 技术构建。 这意味着数据既是存储和查询处理,也是基于列的。 通过减少你在 $select 子句中引用的属性集,你可以减少必须扫描的列数并提高查询的整体性能。

例如,以下查询指定工作项的列。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $select=WorkItemId, Title, State

注意

Azure DevOps 支持进程自定义。 一些管理员使用此功能并创建数百个自定义字段。 如果省略子 $select 句,查询将返回所有字段,包括自定义字段。

✔️ 务必在 $expand 子句的 $select 展开选项中指定列

请与$select子句准则一样,在$expand子句内的$select展开选项中指定属性。 很容易忘记,但如果省略它,响应将包含展开对象中的所有属性。

例如,以下查询指定了工作项及其父项的列。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $select=WorkItemId, Title, State
  &$expand=Parent($select=WorkItemId, Title, State)

✔️ 在查询历史工作项数据(RevisedDateSKWorkItemSnapshot实体集)时,请在WorkItemRevisions上定义筛选器

查询历史数据时,你可能对最近一段时间感兴趣(例如 30 天,90 天)。 由于工作项实体的实现方式,您可以方便地编写此类查询以获得优异的性能。 每次更新工作项时,它都会创建一个新的修订版本,并在System.RevisedDate字段中记录此操作,这非常适合用于历史记录筛选器。

在 Analytics 中,修订日期显示在 RevisedDateEdm.DateTimeOffset)和 RevisedDateSKEdm.Int32)属性中。 为了获得最佳性能,请使用后者。 它是日期代理键 并且表示创建修订的日期;对于未完成的活动修订,它具有 null 值。 如果您想要包含{startDate}在内的所有日期,请将以下筛选器添加到查询中。

RevisedDateSK eq null or RevisedDateSK gt {startDateSK}

例如,以下查询返回自 2020 年初以来每天的工作项数。 请注意,除了在 DateSK 列上显而易见的筛选器外,还有在 RevisedDateSK 上的第二个筛选器。 尽管它看起来是冗余的,但它可帮助查询引擎筛选出不在范围内的修订,并显著提高了查询性能。

https://analytics.dev.azure.com/{OrganizationName}/_odata/v1.0/WorkItemSnapshot?
  $apply=
    filter(DateSK gt 20200101)/
    filter(RevisedDateSK eq null or RevisedDateSK gt 20200101)/
    groupby(
      (DateValue), 
      aggregate($count as Count)
    )

注意

当我们正在使用燃尽小组件时,我们提出了此建议。 最初,我们只为DateSK定义了筛选器,但无法使此查询能够为拥有大型数据集的组织很好地扩大规模。 在查询分析期间,我们注意到 DateSK 不能很好地筛选修订。 只有在添加了 RevisedDateSK 筛选器之后,我们才能实现大规模的卓越性能。
~ 产品团队

✔️ 建议对涉及较长时间跨度的趋势分析查询使用每周或每月快照

默认情况下,所有快照表都建模为 每日快照事实 数据表。 如果查询时间范围,则会获取每天的值。 较长的时间范围会导致大量记录。 如果不需要如此高的精度,可以使用每周甚至每月快照。

可以使用其他筛选表达式执行此操作,以删除未完成给定的一周或月份的天数。 考虑到这种情况,请使用已添加到分析功能中的 IsLastDayOfPeriod 属性。 属性的类型为Microsoft.VisualStudio.Services.Analytics.Model.Period,可以用于确定某一天是否属于不同的时间段(例如,周、月等)。

<EnumType Name="Period" IsFlags="true">
  <Member Name="None" Value="0"/>
  <Member Name="Day" Value="1"/>
  <Member Name="WeekEndingOnSunday" Value="2"/>
  <Member Name="WeekEndingOnMonday" Value="4"/>
  <Member Name="WeekEndingOnTuesday" Value="8"/>
  <Member Name="WeekEndingOnWednesday" Value="16"/>
  <Member Name="WeekEndingOnThursday" Value="32"/>
  <Member Name="WeekEndingOnFriday" Value="64"/>
  <Member Name="WeekEndingOnSaturday" Value="128"/>
  <Member Name="Month" Value="256"/>
  <Member Name="Quarter" Value="512"/>
  <Member Name="Year" Value="1024"/>
  <Member Name="All" Value="2047"/>
</EnumType>

由于 Microsoft.VisualStudio.Services.Analytics.Model.Period 被定义为带标志的枚举,因此请使用 OData has 运算符并为期间文字指定完整的类型。

IsLastDayOfPeriod has Microsoft.VisualStudio.Services.Analytics.Model.Period'Month'

例如,以下查询返回每个月最后一天定义的工作项计数。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItemSnapshot?
  $apply=
    filter(IsLastDayOfPeriod has Microsoft.VisualStudio.Services.Analytics.Model.Period'Month')/
    groupby(
      (DateValue), 
      aggregate($count as Count)
    )

✔️ 按标记筛选时,请对工作项使用 Tags 集合属性

可以将TagNames属性与contains函数结合使用,以确定某个工作是否已标记特定标签。 但是,此方法可能会导致查询速度缓慢,尤其是在同时检查多个标记时。 为了获得最佳性能和结果,请改用 Tags 导航属性。

例如,以下查询获取被标记为 {tag} 的所有工作项。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq '{tag}')
  &$select=WorkItemId, Title, State

如果需要筛选多个标记,此方法也非常有效。 例如,以下查询返回具有 {tag1}{tag2} 标签的所有工作项

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq {tag1} or t/TagName eq {tag2})
  &$select=WorkItemId, Title, State

还可以将这些筛选器与“and”运算符组合在一起。 例如,以下查询会获取同时用 {tag1}{tag2} 标记的所有工作项

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq {tag1}) and Tags/any(t:t/TagName eq {tag2})
  &$select=WorkItemId, Title, State

✔️ TagNames 如果要将工作项上的所有标记显示为文本,请使用属性

上一部分所述的导航属性 Tags非常适合筛选。 但是,使用它们会带来一些挑战,因为查询在嵌套集合中返回标记。 数据模型还包含一个TagNames基础属性(Edm.String),我们添加了该属性以简化标签使用场景。 它是一个单一的文本值,包含所有标签的列表,并以分号“;”分隔符分隔。 当你的重点是将标记一起显示时,请使用此属性。 可以将它与前面所述的标记筛选器组合在一起。

例如,以下查询获取被标记为 {tag} 的所有工作项。 它返回组合标记的工作项 ID、标题、状态和文本表示形式。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq '{tag}')
  &$select=WorkItemId, Title, State, TagNames

重要

属性 TagNames 的长度限制为 1024 个字符。 它包含一组符合该限制的标记。 如果工作项具有许多标记或标记很长,则 TagNames 不要包含完整集, Tag 应改用导航属性。

❌ 请勿使用 tolowertoupper 函数执行不区分大小写的比较

如果已与其他系统一起使用,则可能需要使用 tolowertoupper 函数进行不区分大小写的比较。 使用 Analytics 时,所有字符串比较在默认情况下都是不区分大小写的,因此无需应用任何函数来处理。

例如,以下查询获取标有“QUALITY”、“quality”或该词的任何其他大小写组合的所有工作项。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=Tags/any(t:t/TagName eq 'quality')
  &$select=WorkItemId, Title, State, TagNames

❌ 不要将未受限的扩展与 $levels=max 结合使用

OData 能够扩展分层结构的所有级别。 例如,工作项跟踪中有一些元素,可以应用无限扩展。 此操作仅适用于具有少量数据的组织。 对于较大的数据集,它无法很好地缩放。 不要在以下情况下使用它:

  • 你正在使用大型数据集。
  • 你正在开发一个小组件,并且你无法控制小组件安装的位置。

✔️ 务必使用服务器驱动的分页

如果请求发送的集合过大,因此无法在单个响应中传输,分析功能将应用分页。 响应仅包含部分集和允许检索下一部分项集的链接。 此策略在 OData 规范中有所描述 - OData 版本 4.0 第 1 部分:协议 - 服务器驱动的分页。 通过让服务控制分页,你可以获得最佳性能,因为 skiptoken 已针对每个实体精心设计,以尽可能高效。

属性中包含 @odata.nextLink 指向下一页的链接。

{
  "@odata.context": "https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/$metadata#WorkItems(*)",
  "value": [
    ...
  ],
  "@odata.nextLink":"https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?$skiptoken=12345"}

注意

大多数现有的 OData 客户端都可以自动处理由服务器驱动的分页。 例如,以下工具已使用此策略:Power BI、SQL Server Integration Services 和Azure 数据工厂。

❌ 请勿使用 $top$skip 查询选项来实现客户端驱动的分页

使用其他 REST API 时,你可能已通过使用 `$top` 和 `$skip` 查询选项实现了客户端驱动的分页。 不要将它们与 Analytics 一起使用。 此方法存在几个问题,性能是其中之一。 请改用上一节中所述的服务器驱动的分页策略。

✔️ DO 使用 $top 查询选项限制记录数

只有在与 $skip 一起使用时,才不建议使用查询选项 $top。 如果在报告方案中,只需要一部分记录(例如示例),则可以使用 $top 查询选项。 此外,如果需要根据某些条件对记录进行排名,应始终结合使用$top$orderby来获得排名靠前的记录的稳定结果。

✔️ 考虑编写查询以返回少量记录

编写查询以返回少量记录是最直观的准则。 始终只提取你真正关心的数据。 可以通过充分利用 OData 查询语言中提供的功能强大的筛选功能来实现此目的。

✔️ 考虑将所选属性的数量限制为最小值

某些项目管理员通过添加自定义字段来严重自定义其流程。 在提取宽实体上的所有可用列(例如,例如, WorkItems)时,大量自定义可能会导致性能问题。 分析基于 列存储索引 技术构建。 这意味着数据既是存储和查询处理,也是基于列的。 因此,查询引用的属性越多,处理成本就越高。 始终尽量将查询中的属性集限制为在报表场景中真正需要的属性。

✔️ 考虑筛选日期代理键属性(DateSK 后缀)

可通过多种方式定义日期筛选器。 可以直接筛选日期属性(例如, CreatedDate)、其导航对应项(例如, CreatedOnDate)或其代理项键表示形式(例如, CreatedDate)。 最后一个选项会生成最佳性能,在报告要求允许时首选。

例如,以下查询获取自 2020 年初以来创建的所有工作项。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDateSK ge 20200101

✔️ 考虑对代理键列进行筛选

如果要筛选相关对象值上的数据(例如,筛选项目名称上的工作项),始终有两个选项。 可以使用导航属性(例如 Project/ProjectName),或者提前捕获代理键,并直接在查询中使用它(例如 ProjectSK)。

如果要生成小组件,建议使用后一个选项。 当密钥作为查询的一部分传递时,必须触摸的实体集数会减少,并且性能会提高。

例如,以下查询使用 ProjectSK 属性而不是 Project/ProjectName 导航属性来筛选 WorkItems

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=ProjectSK eq {projectSK}

❌避免在$filter$expand子句中使用ParentChildrenRevisions属性

工作项是整个数据模型中最昂贵的实体。 它们具有多个导航属性,可用于访问相关工作项:Parent、、ChildrenRevisions。 然而,每次在查询中使用它们时,预计性能会下降。 始终质疑您是否真的需要其中一个属性,并可能需要更新您的设计。

例如,可以提取更多工作项,并使用 ParentWorkItemId 属性在客户端重新构造完整的层次结构,而不是展开 Parent。 逐个执行此类优化。

✔️ 考虑在标头中传递 VSTS.Analytics.MaxSize 首选项

执行查询时,不知道查询返回的记录数。 发送另一个包含聚合的查询,或跟踪所有下一个链接并提取整个数据集。 分析功能遵循 VSTS.Analytics.MaxSize 首选项,这样就可以在数据集超过了客户端的可接受范围时快速识别并终止。

此选项在数据导出方案中很有用。 若要使用它,必须向 HTTP 请求添加 Prefer 标头并设置为 VSTS.Analytics.MaxSize 非负值。 该值 VSTS.Analytics.MaxSize 表示可接受的最大记录数。 如果将其设置为零,则使用默认值 200 K。

例如,如果数据集较小或等于 1000 条记录,则以下查询返回工作项。

GET https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems HTTP/1.1
User-Agent: {application}
Prefer: VSTS.Analytics.MaxSize=1000
OData-MaxVersion: 4.0
Accept: application/json;odata.metadata=minimal
Host: analytics.dev.azure.com/{OrganizationName}

如果数据集超过 1000 条记录的限制,查询将立即失败,并出现以下错误。

查询结果包含 1,296 行,超过允许的最大大小 1000。 通过应用其他筛选器减少记录数

有关设置最大页面大小的信息,请参阅 ODataPreferenceHeader.MaxPageSize 属性

查询样式指南

✔️ 务必在聚合方法中使用 $count 虚拟属性

某些实体公开 Count 属性。 当将数据导出到其他存储时,它们使一些报告方案更容易。 但是,不应在 OData 查询中的聚合中使用这些列。 请改用 $count 虚拟属性。

例如,以下查询返回工作项总数。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $apply=aggregate($count as Count)

❌ 避免在 URL 段中使用 $count 虚拟属性

尽管 OData 标准允许对实体集使用 $count 虚拟属性(例如), _odata/v1.0/WorkItems/$count但并非所有客户端都可以正确解释响应。 因此,建议改用聚合方法。

例如,以下查询返回工作项总数。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $apply=aggregate($count as Count)

✔️ 考虑使用参数别名分隔查询的可变部分

参数别名提供了一种优雅的解决方案,用于从主查询文本中提取可变部分(如参数值)。 可以在计算结果的表达式中使用它们:

  • 基元值
  • 复杂值
  • 基元或复杂值的集合。

有关详细信息,请参阅 OData 版本 4.0。第 2 部分:URL 约定 - 5.1.1.13 参数别名。 当查询文本用作可以使用用户提供的值实例化的模板时,参数非常有用。

例如,以下查询使用 @createdDateSK 参数将值与筛选器表达式分开。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=CreatedDateSK ge @createdDateSK
  &$select=WorkItemId, Title, State
  &@createdDateSK=20200101

❌ 避免在单个查询中混合 $apply$filter 子句

如果您想在查询中添加filter,您有两个选项。 您可以使用 $filter 子句或 $apply=filter() 组合来执行此操作。 这些选项中的每一个单独使用都非常出色,但将它们组合在一起可能会导致一些意想不到的结果。

尽管可能会有预期,OData 清楚地定义了评估的顺序。 此外,$apply 条款优先于 $filter 条款。 因此,应选择一个或多个筛选器选项,但在单个查询中避免这两个筛选器选项。 如果自动生成查询,这一点很重要。

例如,以下查询首先根据StoryPoint gt 5筛选工作项,然后按照“路径”聚合结果,最后依据StoryPoints gt 2再次筛选结果。 使用此计算顺序,查询始终返回一个空集。

https://analytics.dev.azure.com/{OrganizationName}/_odata/{version}/WorkItems?
  $filter=StoryPoints gt 2
  $apply=
    filter(StoryPoints gt 5)/
    groupby(
      (Area/AreaPath),
      aggregate(StoryPoints with sum as StoryPoints)
    )

✔️ 考虑将查询结构化以匹配 OData 评估顺序

由于单个查询中的混合 $applyfilter 子句可能会导致潜在的混淆,因此我们建议构建查询子句以匹配计算顺序。

  1. $apply
  2. $filter
  3. $orderby
  4. $expand
  5. $select
  6. $skip
  7. $top

✔️ 考虑查看元数据注释中所述的 OData 功能

不确定哪些 OData 功能分析支持时,可以在元数据中查找批注。 TC GitHub 存储库中的 OASIS 开放数据协议 (OData) 技术委员会维护可用批注的列表。

例如,支持的筛选器函数列表在实体容器上的批注中 Org.OData.Capabilities.V1.FilterFunctions 可用。

<Annotation Term="Org.OData.Capabilities.V1.FilterFunctions">
  <Collection>
  <String>contains</String>
  <String>endswith</String>
  [...]
  </Collection>
</Annotation>

另一个有用的批注是 Org.OData.Capabilities.V1.ExpandRestrictions,它指明了哪些导航属性在 $expand 子句中不能使用。 例如,以下批注说明RevisionsWorkItems实体集中无法展开。

<EntitySet Name="WorkItems" EntityType="Microsoft.VisualStudio.Services.Analytics.Model.WorkItem">
  [...]
  <Annotation Term="Org.OData.Capabilities.V1.ExpandRestrictions">
    <Record>
      <PropertyValue Property="Expandable" Bool="true"/>
      <PropertyValue Property="NonExpandableProperties">
        <Collection>
          <NavigationPropertyPath>Revisions</NavigationPropertyPath>
        </Collection>
      </PropertyValue>
    </Record>
  </Annotation>
</EntitySet>