Azure DevOps 的 OData Analytics 查询指南

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

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

注意

分析服务在所有 Azure DevOps Services 的生产中自动启用和支持。 对 Analytics Service 的 OData 源的 Power BI 集成和访问已正式发布。 我们鼓励你使用它并提供反馈。 可用数据依赖于版本。 支持的最新版本是 v2.0,最新的预览版本是 v4.0-preview。 有关详细信息,请参阅 OData API 版本控制

注意

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

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

注意

Analytics 服务为 Azure DevOps Server 2019 提供预览版。 可以为 项目集合启用或安装它Power BI 集成和分析服务的 OData 源的访问权限处于预览状态。 我们鼓励你使用它并提供反馈。

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

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

提示

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

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

错误和警告消息

✔️ 查看 OData 响应警告

针对一组预定义规则检查执行的每个查询。 冲突返回以下 @vsts.warningsOData 响应。 查看这些警告,因为它们提供有关如何改进查询的当前和上下文敏感信息。

{
  "@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”实体中有权访问的项目(s)。 如果使用$expand或导航属性,则需要这些实体的项目筛选器。

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

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

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)

相反,可以移动筛选器以 $filter 展开子句中的 $expand 选项。 但是,它会更改查询的语义。 例如,以下查询从给定项目获取所有链接,并且仅在目标存在于同一项目中时才有条件地展开目标。 虽然有效,但此方法可能会导致混淆,因为可能很难确定属性是否未展开, 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)

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

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))

若要了解有关聚合的详细信息,请参阅 聚合数据

✔️ DateSK 对快照表进行聚合时,请在子句中包含groupbyDateValue

由于所有快照实体都建模为每日快照表,因此应始终在分组子句中包含一天属性(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 组件。 属性位于 . 的元素中KeyEntityType。 例如, WorkItemId 实体 Revision 的键列 WorkItemRevision

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

❌[已阻止]不要在实体上WorkItem展开Revisions

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

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:指定的查询返回高于允许的 800 列限制的“N”列。 请使用显式$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 已针对此类查询进行优化,因此请使用它。

若要了解详细信息,请参阅 聚合数据

✔️ DO 指定子句中的 $select

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

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

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

注意

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

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

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

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

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

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

查询历史数据时,你可能对最近一段时间感兴趣(例如 30 天,90 天)。 由于工作项实体是如何实现的,因此编写此类查询以获得出色的性能提供了一种便捷的方法。 每次更新工作项时,它都会在字段中创建新的修订并记录此操作 System.RevisedDate ,这使得它非常适合历史记录筛选器。

在 Analytics 中,修订日期显示在 (Edm.DateTimeOffset) 和 RevisedDateSKEdm.Int32) 属性中RevisedDate。 为了获得最佳性能,请使用后者。 它是日期 代理项, 它表示创建修订或具有 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)
    )

注意

当我们正在处理 Burndown 小组件时,我们提出了此建议。 最初,我们只 DateSK 定义了筛选器,但无法获取此查询,以便为具有大型数据集的组织很好地扩展。 在查询分析期间,我们注意到 DateSK 不会很好地筛选修订。 只有在我们添加了筛选器 RevisedDateSK 之后,我们才能大规模获得出色的性能。
~ 产品团队

✔️ 对于长时间内的趋势查询,请使用每周或每月快照

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

可以使用其他筛选表达式执行此操作,以删除未完成给定的一周或月份的天数。 考虑到 IsLastDayOfPeriod 此方案,请使用已添加到 Analytics 的属性。 此属性的类型, 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 导航属性。

例如,以下查询获取使用 a {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),我们添加了该属性以简化标记使用方案。 它是一个文本值,其中包含与分号“; 分隔符合并的所有标记的列表。 当你关心的是一起显示标记时,请使用此属性。 可以将它与前面所述的标记筛选器组合在一起。

例如,以下查询获取使用 a {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 能够扩展分层结构的所有级别。 例如,工作项跟踪具有一些实体,其中可以应用未绑定的扩展。 此操作仅适用于具有少量数据的组织。 对于较大的数据集,它无法很好地缩放。 如果:

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

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

如果要求在单个响应中发送太大的集,Analytics 将应用分页。 响应仅包含部分集和允许检索下一部分项集的链接。 此策略在 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}

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

工作项是整个数据模型中最昂贵的实体。 它们具有多个导航属性,可用于访问相关工作项: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 查询,有两个选项。 可以使用子句或$apply=filter()组合执行此操作$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>