通过 F# 开始使用 Azure 表存储和 Azure Cosmos DB 表 API

Azure 表存储是一种将结构化的 NoSQL 数据存储在云中的服务。 表存储是采用无架构设计的键/属性存储。 因为表存储无架构,因此可以很容易地随着应用程序需求的发展使数据适应存储。 对于所有类型的应用程序,都可以快速并经济高效地访问数据。 对于相似的数据量,表存储的成本通常显著低于传统的 SQL。

可以使用表存储来存储灵活的数据集,例如 Web 应用程序的用户数据、通讯簿、设备信息,以及服务需要的任何其他类型的元数据。 可以在表中存储任意数量的实体,并且一个存储帐户可以包含任意数量的表,直至达到存储帐户的容量极限。

Azure Cosmos DB 提供的表 API 适用于为 Azure 表存储编写且需要以下高级功能的应用程序:

  • 统包式全局分发。
  • 全球范围内专用的吞吐量。
  • 99% 的情况下低至个位数的毫秒级延迟。
  • 保证高可用性。
  • 自动编制辅助索引。

为 Azure 表存储编写的应用程序无需更改代码便可使用表 API 迁移到 Azure Cosmos DB,并可充分利用高级功能。 表 API 包含可用于 .NETJavaPythonNode.js 的客户端 SDK。

有关详细信息,请参阅 Azure Cosmos DB Table API 简介

关于本教程

本教程介绍如何编写 F# 代码,以使用 Azure 表存储或 Azure Cosmos DB 表 API 执行一些常见任务,包括创建和删除表以及插入、更新、删除和查询表数据。

先决条件

若要使用本指南,必须先创建 Azure 存储帐户Azure Cosmos DB 帐户

创建 F# 脚本并启动 F# 交互

本文中的示例可用于 F# 应用程序或 F# 脚本。 若要创建 F# 脚本,请在 F# 开发环境中创建一个扩展名为 .fsx 的文件,例如 tables.fsx

如何执行脚本

F# 交互窗口 dotnet fsi 可通过交互方式启动,也可以从命令行启动,以运行脚本。 命令行语法是

> dotnet fsi [options] [ script-file [arguments] ]

在脚本中添加包

接下来,使用 #rnuget:package name 安装 Azure.Data.Tables 包和 open 命名空间。 如

> #r "nuget: Azure.Data.Tables"
open Azure.Data.Tables

添加命名空间声明

将下列 open 语句添加到 tables.fsx 文件顶部:

open System
open Azure
open Azure.Data.Tables // Namespace for Table storage types

获取 Azure 存储连接字符串

如果要连接到 Azure 存储表服务,则在本教程中需要连接字符串。 可以从 Azure 门户复制连接字符串。 有关连接字符串的详细信息,请参阅配置存储连接字符串

获取 Azure Cosmos DB 连接字符串

如果要连接到 Azure Cosmos DB,则在本教程中需要连接字符串。 可以从 Azure 门户复制连接字符串。 在 Azure 门户的 Cosmos DB 帐户中,转到“设置”>“连接字符串”,选择“复制”按钮来复制主连接字符串。

在本教程中,在脚本中输入连接字符串,如下所示:

let storageConnString = "UseDevelopmentStorage=true" // fill this in from your storage account

创建表服务客户端

TableServiceClient 类用于检索表存储中的表和实体。 下面是创建服务客户端的一种方法:

let tableClient = TableServiceClient storageConnString

现在,已准备好编写从表存储读取数据并将数据写入表存储的代码。

创建表

此示例演示如何创建表(如果表已经不存在):

// Retrieve a reference to the table.
let table = tableClient.GetTableClient "people"

// Create the table if it doesn't exist.
table.CreateIfNotExists () |> ignore

将实体添加到表

实体必须具有实现 ITableEntity 的类型。 虽然可以按任意方式扩展 ITableEntity,但类型必须具有无参数构造函数。 只有同时具有 getset 的属性存储在 Azure 表中。

实体的分区键和行键用于唯一地标识表中的实体。 查询分区键相同的实体的速度快于查询分区键不同的实体的速度,但使用不同的分区键可实现更高的并行操作可伸缩性。

以下 Customer 示例使用 lastName 作为分区键,使用 firstName 作为行键。

type Customer (firstName, lastName, email: string, phone: string) =
    interface ITableEntity with
        member val ETag = ETag "" with get, set
        member val PartitionKey = "" with get, set
        member val RowKey = "" with get, set
        member val Timestamp = Nullable() with get, set

    new() = Customer(null, null, null, null)
    member val Email = email with get, set
    member val PhoneNumber = phone with get, set
    member val PartitionKey = lastName with get, set
    member val RowKey = firstName with get, set

立即将 Customer 添加到表中。 为此,可以使用 AddEntity () 方法。

let customer = Customer ("Walter", "Harp", "Walter@contoso.com", "425-555-0101")
table.AddEntity customer

插入一批实体

可以通过单次写入操作将一批实体插入表中。 批处理操作允许将操作合并为单个执行,但它们有一些限制:

  • 你可以在同一批处理操作中执行更新、删除和插入操作。
  • 一个批处理操作最多可包含 100 个实体。
  • 一个批处理操作中的所有实体都必须具有相同的分区键。
  • 虽然可以在一个批处理操作执行查询,但该操作必须是批处理中仅有的操作。

下面是将两个插入合并到一个批处理操作中的部分代码:

let customers =
    [
        Customer("Jeff", "Smith", "Jeff@contoso.com", "425-555-0102")
        Customer("Ben", "Smith", "Ben@contoso.com", "425-555-0103")
    ]

// Add the entities to be added to the batch and submit it in a transaction.
customers
|> List.map (fun customer -> TableTransactionAction (TableTransactionActionType.Add, customer))
|> table.SubmitTransaction

检索分区中的所有实体

若要查询表以获取某个分区中的所有实体,请使用 Query<T> 对象。 在这里,可以筛选出“Smith”是分区键的实体。

table.Query<Customer> "PartitionKey eq 'Smith'"

检索分区中的一部分实体

如果不想查询分区中的所有实体,则可以通过结合使用分区键筛选器与行键筛选器来指定一个范围。 在这里,可以使用两个过滤器来获取“Smith”分区中行键(名字)以字母表中“M”之前的字母开头的所有实体。

table.Query<Customer> "PartitionKey eq 'Smith' and RowKey lt 'J'"

检索单个实体

若要检索单个特定实体,请使用 GetEntityAsync 指定客户“Ben Smith”。 你将获得 Customer,而不是集合。 在查询中同时指定分区键和行键是从表服务中检索单个实体的最快方法。

let singleResult = table.GetEntity<Customer>("Smith", "Ben").Value

现在输出结果:

// Evaluate this value to print it out into the F# Interactive console
singleResult

更新条目

若要更新实体,请从表服务中检索它,修改实体对象,然后使用 TableUpdateMode.Replace 操作将更改保存回表服务。 这将导致在服务器上完全替换该实体,除非服务器上的该实体自检索到它以后发生更改,在此情况下,该操作将失败。 这种失败是为了防止应用程序无意中覆盖来自其他源的更改。

singleResult.PhoneNumber <- "425-555-0103"
try
    table.UpdateEntity (singleResult, ETag "", TableUpdateMode.Replace) |> ignore
    printfn "Update succeeded"
with
| :? RequestFailedException as e ->
    printfn $"Update failed: {e.Status} - {e.ErrorCode}"

更新插入实体

有时,你不知道表中是否存在实体。 如果存在,则不再需要存储在其中的当前值。 无论实体的状态如何,只要它存在,都可以使用 UpsertEntity 方法来创建或替换实体。

singleResult.PhoneNumber <- "425-555-0104"
table.UpsertEntity (singleResult, TableUpdateMode.Replace)

查询一部分实体属性

表查询可以只检索实体中的少数几个属性而不是所有实体属性。 此方法称为“投影”,可提高查询性能,尤其适用于大型实体。 在这里,你将使用 Query<T>Select 仅返回电子邮件地址。 本地存储模拟器不支持投影,因此,此代码仅在使用表服务中的帐户时才能运行。

query {
    for customer in table.Query<Customer> () do
    select customer.Email
}

以异步方式检索页中的实体

如果你正在读取大量实体,并且想要在检索实体时处理实体,而非等待返回全部实体,则可以使用分段查询。 在这里,你将使用一个异步工作流在页面中返回结果,这样当你在等待一大组结果返回时,执行就不会被阻断。

let pagesResults = table.Query<Customer> ()

for page in pagesResults.AsPages () do
    printfn "This is a new page!"
    for customer in page.Values do
        printfn $"customer: {customer.RowKey} {customer.PartitionKey}"

删除条目

你可以在检索到实体后将其删除。 与更新实体一样,如果实体自检索以来已更改,此操作将失败。

table.DeleteEntity ("Smith", "Ben")

删除表

可以从存储帐户中删除表。 在删除表之后的一段时间内无法重新创建它。

table.Delete ()

另请参阅