TripPin 第 2 部分 - REST 服务的数据连接器

此多部分教程介绍如何为 Power Query 创建新的数据源扩展。 本教程旨在按顺序完成 — 每个课程都基于在上一课中创建的连接器上构建,以增量方式向连接器添加新功能。

在本课中,你将:

  • 使用 Web.Contents 创建调用 REST API 的基本函数
  • 了解如何设置请求标头并处理 JSON 响应
  • 使用 Power BI Desktop 将响应划分为用户友好格式

本课将 TripPin 服务的 基于 OData 的连接器( 在上一课中创建)转换为类似于为任何 RESTful API 创建的连接器。 OData 是 RESTful API,但具有固定约定集的 API。 OData 的优点是它提供架构、数据检索协议和标准查询语言。 删除 OData.Feed 的使用要求你自己在连接器中构建这些功能。

OData 连接器的综述

在从连接器中删除 OData 函数之前,让我们快速浏览一下它目前为了从服务检索数据而执行的工作(大部分是在幕后进行的)。

在 Visual Studio Code 中从 第 1 部分 打开 TripPin 扩展项目。 打开查询文件并粘贴以下查询:

TripPin.Feed("https://services.odata.org/v4/TripPinService/Me")

打开 Fiddler,然后在 Visual Studio Code 中评估当前 Power Query 文件。

在 Fiddler 中,服务器有三个请求:

Fiddler 输出屏幕上显示三个 OData 请求的截图。

  • /Me:要请求的实际 URL。
  • /$metadataOData.Feed 函数自动进行的一次调用,用于确定响应的架构和类型信息。
  • /Me/BestFriend:在列出 /Me 单例时(急切地)拉取的字段之一。 在这种情况下,调用导致 204 No Content 状态。

M 评估大多是懒惰的。 在大多数情况下,仅在需要时检索/拉取数据值。 在某些情况下(如 /Me/BestFriend 案例),值会被立即获取。 当需要获取一个成员的类型信息时,此行为往往会发生,因为引擎除了检索并检查其值外,没有其他方法来确定类型。 延迟执行操作(即,避免急切的拉取)是提升 M 连接器性能的关键之一。

查看随请求一起发送的请求标头和 /Me 请求响应的 JSON 格式。

{
  "@odata.context": "https://services.odata.org/v4/TripPinService/$metadata#Me",
  "UserName": "aprilcline",
  "FirstName": "April",
  "LastName": "Cline",
  "MiddleName": null,
  "Gender": "Female",
  "Age": null,
  "Emails": [ "April@example.com", "April@contoso.com" ],
  "FavoriteFeature": "Feature1",
  "Features": [ ],
  "AddressInfo": [
    {
      "Address": "P.O. Box 555",
      "City": {
        "Name": "Lander",
        "CountryRegion": "United States",
        "Region": "WY"
      }
    }
  ],
  "HomeAddress": null
}

查询评估完成后,应在 PQTest 结果窗口中显示 Me 单例对象的记录数值。

PQTest 结果的屏幕截图,其中显示了 Me 单例的记录值。

如果将输出窗口中的字段与原始 JSON 响应中返回的字段进行比较,你会注意到不匹配。 查询结果包含 JSON 响应中未出现在任何位置的其他字段(FriendsTripsGetFriendsTrips)。 OData.Feed 函数根据$metadata返回的架构自动将这些字段追加到记录。 此差异是连接器如何增强和/或重新设置服务响应以提供更好的用户体验的良好示例。

创建基本 REST 连接器

现在添加一个新的导出函数到你的连接器,该函数调用 Web.Contents

但是,若要成功向 OData 服务发出 Web 请求,必须设置一些 标准 OData 标头。 为此,请将一组常见的标头定义为连接器中的新变量:

DefaultRequestHeaders = [
    #"Accept" = "application/json;odata.metadata=minimal",  // column name and values only
    #"OData-MaxVersion" = "4.0"                             // we only support v4
];

更改TripPin.Feed函数的实现,改为使用Web.Contents进行 Web 请求,而不是使用OData.Feed,并将结果解析为 JSON 文档。

TripPinImpl = (url as text) =>
    let
        source = Web.Contents(url, [ Headers = DefaultRequestHeaders ]),
        json = Json.Document(source)
    in
        json;

请记得在对连接器文件进行更改后生成连接器。 然后,可以评估查询文件(TripPin.query.pq)。 /Me 记录的结果现在类似于 Fiddler 请求中的原始 JSON。

如果在运行新函数时监视 Fiddler,则还注意到评估现在发出单个 Web 请求,而不是 3 个。 恭喜,你实现了 300% 性能提升! 现在,你失去了所有类型和架构信息,但目前无需专注于该部分。

更新查询以访问某些 TripPin 实体/表,例如:

  • https://services.odata.org/v4/TripPinService/Airlines
  • https://services.odata.org/v4/TripPinService/Airports
  • https://services.odata.org/v4/TripPinService/Me/Trips

你注意到,用于返回格式良好的表的路径现在返回带有嵌入 [List] 的顶级“值”字段。 你需要对结果执行一些转换,使其可用于最终用户使用方案。

包含嵌入列表的值字段的 PQTest 结果的屏幕截图。

在 Power Query 中编写转换

虽然可以手动创作 M 转换,但大多数人更喜欢使用 Power Query 来塑造其数据。 在 Power BI Desktop 中打开扩展,并使用它设计查询,以将输出转换为更用户友好的格式。 重新生成解决方案,将新的扩展文件复制到自定义数据连接器目录,然后重新启动 Power BI Desktop。

启动新的空白查询,并将以下行粘贴到公式栏中:

= TripPin.Feed("https://services.odata.org/v4/TripPinService/Airlines")

请务必包含 = 符号。

调整输出,使其看起来像原始 OData 数据流:一个包含两列的表格,即 AirlineCode 和 Name。

包含 AirlineCode 和 Name 列的 Power Query 表的屏幕截图。

生成的查询应如下所示:

let
    Source = TripPin.Feed("https://services.odata.org/v4/TripPinService/Airlines"),
    value = Source[value],
    toTable = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    expand = Table.ExpandRecordColumn(toTable, "Column1", {"AirlineCode", "Name"}, {"AirlineCode", "Name"})
in
    expand

为查询命名(“航空公司”)。

创建新的空白查询。 这一次,使用 TripPin.Feed 函数访问 /Airports 实体。 应用转换,直到获得类似于下表的内容。 匹配的查询遵循该表,同时为此查询命名(“Airports”)。

包含已转换表的 Power Query 表的屏幕截图。

let
    Source = TripPin.Feed("https://services.odata.org/v4/TripPinService/Airports"),
    value = Source[value],
    #"Converted to Table" = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"Name", "IcaoCode", "IataCode", "Location"}, {"Name", "IcaoCode", "IataCode", "Location"}),
    #"Expanded Location" = Table.ExpandRecordColumn(#"Expanded Column1", "Location", {"Address", "Loc", "City"}, {"Address", "Loc", "City"}),
    #"Expanded City" = Table.ExpandRecordColumn(#"Expanded Location", "City", {"Name", "CountryRegion", "Region"}, {"Name.1", "CountryRegion", "Region"}),
    #"Renamed Columns" = Table.RenameColumns(#"Expanded City",{{"Name.1", "City"}}),
    #"Expanded Loc" = Table.ExpandRecordColumn(#"Renamed Columns", "Loc", {"coordinates"}, {"coordinates"}),
    #"Added Custom" = Table.AddColumn(#"Expanded Loc", "Latitude", each [coordinates]{1}),
    #"Added Custom1" = Table.AddColumn(#"Added Custom", "Longitude", each [coordinates]{0}),
    #"Removed Columns" = Table.RemoveColumns(#"Added Custom1",{"coordinates"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Removed Columns",{{"Name", type text}, {"IcaoCode", type text}, {"IataCode", type text}, {"Address", type text}, {"City", type text}, {"CountryRegion", type text}, {"Region", type text}, {"Latitude", type number}, {"Longitude", type number}})
in
    #"Changed Type"

可以在服务下对更多路径重复此过程。 准备就绪后,转到创建(模拟)导航表的下一步。

模拟导航表

现在,你将生成一个表(使用 M 代码)来呈现格式良好的 TripPin 实体。

启动新的空白查询并启动高级编辑器。

将以下查询粘贴到相应位置:

let
    source = #table({"Name", "Data"}, {
        { "Airlines", Airlines },
        { "Airports", Airports }
    })
in
    source

如果未将隐私级别设置设置为“始终忽略隐私级别设置”(也称为“快速合并”),则会显示隐私提示。

Power Query 隐私提示的屏幕截图。

合并来自多个源的数据时,将显示隐私提示,但未为一个或多个源指定隐私级别。 选择“ 继续 ”按钮,并将顶部源的隐私级别设置为 “公共”。

“隐私级别”对话框的屏幕截图,其中将顶级源的隐私级别设置为“公共”。

选择“ 保存 ”,此时会显示表。 虽然此表尚不是导航表,但它提供了在后续课程中将其转换为所需的基本功能。

包含 TripPin 数据的表的屏幕截图。

从扩展内访问多个数据源时,不会进行数据组合检查。 由于从扩展内发出的所有数据源调用都继承了相同的授权上下文,因此假设它们是“安全”组合的。 在数据组合规则中,你的扩展始终被视为单一的数据源。 当将您的源与其他 M 源合并时,用户仍会收到常规的隐私提示。

如果运行 Fiddler 并选择 Power Query 编辑器中的 “刷新预览 ”按钮,请注意导航表中每个项的单独 Web 请求。 这些单独的请求表明存在过于急切的评估,而这在生成包含大量元素的导航表时并不理想。 后续课程演示如何构建支持延迟评估的正确导航表。

结论

本课程介绍了如何为 REST 服务生成简单的连接器。 在这种情况下,将现有 OData 扩展转换为标准 REST 扩展(使用 Web.Contents),但如果从头开始创建新的扩展,则相同的概念适用。

在下一课中,你将学习使用 Power BI Desktop 在本课中创建的查询,并将其转换为扩展中的真实导航表。

后续步骤

TripPin 第 3 部分 - 导航表