适用于 Azure Functions 的 Azure 表输入绑定

使用 Azure 表输入绑定读取 Azure Cosmos DB for TableAzure 表存储中的表。

有关设置和配置详细信息,请参阅概述

重要

本文使用选项卡来支持多个版本的 Node.js 编程模型。 v4 模型目前处于预览状态,旨在为 JavaScript 和 TypeScript 开发人员提供更为灵活和直观的体验。 在升级指南中详细了解 v3 和 v4 之间的差异。

示例

绑定的用法取决于扩展包版本,以及函数应用中使用的 C# 形式,可以是以下形式之一:

独立工作进程类库的已编译 C# 函数在独立于运行时的进程中运行。

选择一个版本以查看模式和版本的示例。

下面的 MyTableData 类表示表中的一行数据:

public class MyTableData : Azure.Data.Tables.ITableEntity
{
    public string Text { get; set; }

    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public DateTimeOffset? Timestamp { get; set; }
    public ETag ETag { get; set; }
}

以下函数由队列存储触发器启动,它从队列中读取行键,该键用于从输入表中获取行。 表达式 {queueTrigger} 将行键绑定到消息元数据(即消息字符串)。

[Function("TableFunction")]
[TableOutput("OutputTable", Connection = "AzureWebJobsStorage")]
public static MyTableData Run(
    [QueueTrigger("table-items")] string input,
    [TableInput("MyTable", "<PartitionKey>", "{queueTrigger}")] MyTableData tableInput,
    FunctionContext context)
{
    var logger = context.GetLogger("TableFunction");

    logger.LogInformation($"PK={tableInput.PartitionKey}, RK={tableInput.RowKey}, Text={tableInput.Text}");

    return new MyTableData()
    {
        PartitionKey = "queue",
        RowKey = Guid.NewGuid().ToString(),
        Text = $"Output record with rowkey {input} created at {DateTime.Now}"
    };
}

以下由队列触发的函数将前 5 个实体作为 IEnumerable<T> 返回,分区键值设置为队列消息。

[Function("TestFunction")]
public static void Run([QueueTrigger("myqueue", Connection = "AzureWebJobsStorage")] string partition,
    [TableInput("inTable", "{queueTrigger}", Take = 5, Filter = "Text eq 'test'", 
    Connection = "AzureWebJobsStorage")] IEnumerable<MyTableData> tableInputs,
    FunctionContext context)
{
    var logger = context.GetLogger("TestFunction");
    logger.LogInformation(partition);
    foreach (MyTableData tableInput in tableInputs)
    {
        logger.LogInformation($"PK={tableInput.PartitionKey}, RK={tableInput.RowKey}, Text={tableInput.Text}");
    }
}

FilterTake 属性用于限制返回的实体数。

以下示例显示了一个 HTTP 触发函数,该函数返回表存储中指定分区中的 person 对象列表。 在此示例中,分区键将从 http 路由中提取,表名和连接将从函数设置中提取。

public class Person {
    private String PartitionKey;
    private String RowKey;
    private String Name;

    public String getPartitionKey() { return this.PartitionKey; }
    public void setPartitionKey(String key) { this.PartitionKey = key; }
    public String getRowKey() { return this.RowKey; }
    public void setRowKey(String key) { this.RowKey = key; }
    public String getName() { return this.Name; }
    public void setName(String name) { this.Name = name; }
}

@FunctionName("getPersonsByPartitionKey")
public Person[] get(
        @HttpTrigger(name = "getPersons", methods = {HttpMethod.GET}, authLevel = AuthorizationLevel.FUNCTION, route="persons/{partitionKey}") HttpRequestMessage<Optional<String>> request,
        @BindingName("partitionKey") String partitionKey,
        @TableInput(name="persons", partitionKey="{partitionKey}", tableName="%MyTableName%", connection="MyConnectionString") Person[] persons,
        final ExecutionContext context) {

    context.getLogger().info("Got query for person related to persons with partition key: " + partitionKey);

    return persons;
}

TableInput 注释还可以从请求的 json 正文中提取绑定,如下面的示例所示。

@FunctionName("GetPersonsByKeysFromRequest")
public HttpResponseMessage get(
        @HttpTrigger(name = "getPerson", methods = {HttpMethod.GET}, authLevel = AuthorizationLevel.FUNCTION, route="query") HttpRequestMessage<Optional<String>> request,
        @TableInput(name="persons", partitionKey="{partitionKey}", rowKey = "{rowKey}", tableName="%MyTableName%", connection="MyConnectionString") Person person,
        final ExecutionContext context) {

    if (person == null) {
        return request.createResponseBuilder(HttpStatus.NOT_FOUND)
                    .body("Person not found.")
                    .build();
    }

    return request.createResponseBuilder(HttpStatus.OK)
                    .header("Content-Type", "application/json")
                    .body(person)
                    .build();
}

下面的示例使用筛选器在 Azure 表中查询具有特定名称的人员,并将可能的匹配项数目限制为 10 个结果。

@FunctionName("getPersonsByName")
public Person[] get(
        @HttpTrigger(name = "getPersons", methods = {HttpMethod.GET}, authLevel = AuthorizationLevel.FUNCTION, route="filter/{name}") HttpRequestMessage<Optional<String>> request,
        @BindingName("name") String name,
        @TableInput(name="persons", filter="Name eq '{name}'", take = "10", tableName="%MyTableName%", connection="MyConnectionString") Person[] persons,
        final ExecutionContext context) {

    context.getLogger().info("Got query for person related to persons with name: " + name);

    return persons;
}

以下示例显示了使用队列触发器读取单个表行的表输入绑定。 该绑定指定一个 partitionKey 和一个 rowKeyrowKey 值“{queueTrigger}”指示行键来自队列消息字符串。

import { app, input, InvocationContext } from '@azure/functions';

const tableInput = input.table({
    tableName: 'Person',
    partitionKey: 'Test',
    rowKey: '{queueTrigger}',
    connection: 'MyStorageConnectionAppSetting',
});

interface PersonEntity {
    PartitionKey: string;
    RowKey: string;
    Name: string;
}

export async function storageQueueTrigger1(queueItem: unknown, context: InvocationContext): Promise<void> {
    context.log('Node.js queue trigger function processed work item', queueItem);
    const person = <PersonEntity>context.extraInputs.get(tableInput);
    context.log('Person entity name: ' + person.Name);
}

app.storageQueue('storageQueueTrigger1', {
    queueName: 'myqueue-items',
    connection: 'MyStorageConnectionAppSetting',
    extraInputs: [tableInput],
    handler: storageQueueTrigger1,
});
const { app, input } = require('@azure/functions');

const tableInput = input.table({
    tableName: 'Person',
    partitionKey: 'Test',
    rowKey: '{queueTrigger}',
    connection: 'MyStorageConnectionAppSetting',
});

app.storageQueue('storageQueueTrigger1', {
    queueName: 'myqueue-items',
    connection: 'MyStorageConnectionAppSetting',
    extraInputs: [tableInput],
    handler: (queueItem, context) => {
        context.log('Node.js queue trigger function processed work item', queueItem);
        const person = context.extraInputs.get(tableInput);
        context.log('Person entity name: ' + person.Name);
    },
});

下面的函数使用队列触发器读取单个表行作为函数的输入。

在此示例中,绑定配置为表的 partitionKey 指定一个显式值,并使用了一个要传递给 rowKey 的表达式。 rowKey 表达式 {queueTrigger} 指示行键来自队列消息字符串。

function.json 中的绑定配置:

{
  "bindings": [
    {
      "queueName": "myqueue-items",
      "connection": "MyStorageConnectionAppSetting",
      "name": "MyQueueItem",
      "type": "queueTrigger",
      "direction": "in"
    },
    {
      "name": "PersonEntity",
      "type": "table",
      "tableName": "Person",
      "partitionKey": "Test",
      "rowKey": "{queueTrigger}",
      "connection": "MyStorageConnectionAppSetting",
      "direction": "in"
    }
  ],
  "disabled": false
}

run.ps1 中的 PowerShell 代码:

param($MyQueueItem, $PersonEntity, $TriggerMetadata)
Write-Host "PowerShell queue trigger function processed work item: $MyQueueItem"
Write-Host "Person entity name: $($PersonEntity.Name)"

下面的函数使用 HTTP 触发器读取单个表行作为函数的输入。

在此示例中,绑定配置为表的 partitionKey 指定一个显式值,并使用了一个要传递给 rowKey 的表达式。 rowKey 表达式 {id} 指示行键来自请求中路由的 {id} 部分。

function.json 文件中的绑定配置:

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "name": "messageJSON",
      "type": "table",
      "tableName": "messages",
      "partitionKey": "message",
      "rowKey": "{id}",
      "connection": "AzureWebJobsStorage",
      "direction": "in"
    },
    {
      "authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": [
        "get",
        "post"
      ],
      "route": "messages/{id}"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ],
  "disabled": false
}

__init__.py 文件中的 Python 代码:

import json

import azure.functions as func

def main(req: func.HttpRequest, messageJSON) -> func.HttpResponse:

    message = json.loads(messageJSON)
    return func.HttpResponse(f"Table row: {messageJSON}")

使用这种简单绑定,无法以编程方式处理没有找到行键 ID 的行。 有关更精细的数据选择,请使用存储 SDK


特性

进程内独立工作进程 C# 库使用特性来定义函数。 C# 脚本改用 function.json 配置文件,如 C# 脚本指南中所述。

C# 类库中,TableInputAttribute 支持以下属性:

Attribute 属性 说明
TableName 表的名称。
PartitionKey 可选。 要读取的表实体的分区键。
RowKey 可选。 要读取的表实体的行键。
Take 可选。 要读取到 IEnumerable<T> 的最大实体数。 无法与 RowKey 一起使用。
Filter 可选。 要读取到 IEnumerable<T> 的实体的 OData 筛选器表达式。 无法与 RowKey 一起使用。
Connection 指定如何连接到表服务的应用设置或设置集合的名称。 请参阅连接

批注

Java 函数运行时库中,对其值将来自表存储的参数使用 @TableInput 注释。 可以将此注释与本机 Java 类型、POJO 或使用了 Optional<T> 的可为 null 的值一起使用。 此批注支持以下元素:

元素 说明
name 表示函数代码中的表或实体的变量的名称。
tableName 表的名称。
partitionKey 可选。 要读取的表实体的分区键。
rowKey 可选。 要读取的表实体的行键。
take 可选。 要读取的实体的最大数目。
filter 可选。 表输入的 OData 筛选表达式。
connection 指定如何连接到表服务的应用设置或设置集合的名称。 请参阅连接

配置

下表说明了可以在传递给 input.table() 方法的 options 对象上设置的属性。

properties 说明
tableName 表的名称。
partitionKey 可选。 要读取的表实体的分区键。
rowKey 可选。 要读取的表实体的行键。 不能与 takefilter 一起使用。
take 可选。 要返回的实体的最大数目。 无法与 rowKey 一起使用。
filter 可选。 用于从表中返回实体的 OData 筛选器表达式。 无法与 rowKey 一起使用。
连接 指定如何连接到表服务的应用设置或设置集合的名称。 请参阅连接

配置

下表解释了在 function.json 文件中设置的绑定配置属性。

“function.json”属性 说明
type 必须设置为 table。 在 Azure 门户中创建绑定时,会自动设置此属性。
direction 必须设置为 in。 在 Azure 门户中创建绑定时,会自动设置此属性。
name 表示函数代码中的表或实体的变量的名称。
tableName 表的名称。
partitionKey 可选。 要读取的表实体的分区键。
rowKey 可选。 要读取的表实体的行键。 不能与 takefilter 一起使用。
take 可选。 要返回的实体的最大数目。 无法与 rowKey 一起使用。
filter 可选。 用于从表中返回实体的 OData 筛选器表达式。 无法与 rowKey 一起使用。
连接 指定如何连接到表服务的应用设置或设置集合的名称。 请参阅连接

在本地开发时,请将应用程序设置添加到 Values 集合的 local.settings.json 文件

连接

connection 属性是对环境配置的引用,它指定应用应该如何连接到表服务。 它可能指定:

如果配置的值既是单个设置的完全匹配,也是其他设置的前缀匹配,则使用完全匹配。

连接字符串

若要在 Azure 表存储中为表获取连接字符串,请执行管理存储帐户访问密钥中显示的步骤。 若要在 Azure Cosmos DB for Table 中为表获取连接字符串,请执行 Azure Cosmos DB for Table 常见问题解答中显示的步骤。

此连接字符串应存储在应用程序设置中,其名称与绑定配置的 connection 属性指定的值匹配。

如果应用设置名称以“AzureWebJobs”开始,则只能在此处指定该名称的余下部分。 例如,如果将 connection 设置为“MyStorage”,则 Functions 运行时会查找名为“AzureWebJobsMyStorage”的应用设置。 如果将 connection 留空,函数运行时将使用名为 AzureWebJobsStorage 的应用设置中的默认存储连接字符串。

基于标识的连接

如果使用的是表 API 扩展,而不是将连接字符串与机密一起使用,则可以将应用设置为使用 Azure Active Directory 标识。 这仅适用于访问 Azure 存储中的表的情况。 要使用标识,需要定义公共前缀下的设置,该前缀映射到触发器和绑定配置中的 connection 属性。

如果将 connection 设置为“AzureWebJobsStorage”,请参阅使用标识连接到主机存储。 对于所有其他连接,扩展需要以下属性:

属性 环境变量模板 说明 示例值
表服务 URI <CONNECTION_NAME_PREFIX>__tableServiceUri1 使用 HTTPS 方案时,要连接到的 Azure 存储表服务的数据平面 URI。 https://<storage_account_name>.table.core.chinacloudapi.cn

1 <CONNECTION_NAME_PREFIX>__serviceUri 可以用作别名。 如果同时提供了两个窗体,则会使用 tableServiceUri 窗体。 如果要跨 Blob、队列和/或表使用总体连接配置,则无法使用 serviceUri 窗体。

可以设置其他属性来自定义连接。 请参阅基于标识的连接的通用属性

如果要在 Azure 存储中跨 Blob、队列和/或表使用总体连接配置,则无法使用 serviceUri 窗体。 URI 只能指定表服务。 作为一种替代方案,可以在同一前缀下为每个服务专门提供一个 URI,从而允许使用单个连接。

在 Azure Functions 服务中托管时,基于标识的连接将使用托管标识。 默认情况下使用系统分配的标识,但可以使用 credentialclientID 属性来指定用户分配的标识。 请注意,不支持为用户分配的标识配置资源 ID。 在其他上下文(如本地开发)中运行时,将改用开发人员标识,尽管可以进行自定义。 请参阅使用基于标识的连接进行本地开发

向标识授予权限

无论使用何种标识,都必须具有执行所需操作的权限。 需要使用内置角色或者提供这些权限的自定义角色在 Azure RBAC 中分配角色

重要

某些权限可能由并非所有上下文都需要的目标服务公开。 尽可能遵循最低权限原则,仅授予标识所需的权限。 例如,如果应用只需要从数据源进行读取即可,则使用仅具有读取权限的角色。 分配一个也具有该服务写入权限的角色并不恰当,因为对于读取操作来说,写入是多余的权限。 同样,你也希望确保角色分配的范围仅限于需要读取的资源。

你将需要创建一个角色分配,以便在运行时提供对 Azure 存储表服务的访问权限。 所有者等管理角色还不够。 下表显示了在正常操作中对 Azure 存储使用 Azure Cosmos DB for Table 扩展时建议使用的内置角色。 根据所编写的代码,应用程序可能需要具有其他权限。

绑定类型 内置角色示例(Azure 存储1
输入绑定 存储表数据读取者
输出绑定 存储表数据参与者

1 如果应用改为连接到 Azure Cosmos DB for Table 中的表,则不支持使用标识,并且连接必须使用连接字符串。

使用情况

绑定的用法取决于扩展包版本,以及函数应用中使用的 C# 形式,可以是以下形式之一:

独立工作进程类库的已编译 C# 函数在独立于运行时的进程中运行。

选择一个版本以查看模式和版本的使用情况详细信息。

如果使用单个表实体,可将 Azure 表输入绑定绑到以下类型:

类型 说明
实现 ITableEntity 的 JSON 可序列化类型 Functions 会尝试将实体反序列化为普通的旧 CLR 对象 (POCO) 类型。 该类型必须实现 ITableEntity 或具有字符串 RowKey 属性和字符串 PartitionKey 属性。
TableEntity (预览版1
作为类似于字典的类型的实体。

如果使用查询中的多个实体,可将 Azure 表输入绑定绑到以下类型:

类型 说明
IEnumerable<T> 其中 T 实现 ITableEntity 查询返回的实体的枚举。 每个条目表示一个实体。 类型 T 必须实现 ITableEntity 或具有字符串 RowKey 属性和字符串 PartitionKey 属性。
TableClient (预览版1
连接到表的客户端。 这提供了对表处理的充分控制,如果连接具有足够的权限,则可用于写入容器。

1 若要使用这些类型,需要引用 Microsoft.Azure.Functions.Worker.Extensions.Tables 1.2.0-preview1 或更高版本以及 SDK 类型绑定的常见依赖项

使用 TableInput 特性可以访问触发了函数的表行。

使用 context.extraInputs.get() 获取输入行数据。

数据将传递给 function.json 文件中 name 键所指定的输入参数。 通过指定 partitionKeyrowKey,可以筛选到特定记录。

表数据将作为 JSON 字符串传递给函数。 通过调用 json.loads 反序列化消息,如输入示例所示。

有关具体使用情况的详细信息,请参阅示例

后续步骤