数据存储

Azure DevOps Services | Azure DevOps Server 2020 | Azure DevOps Server 2019 | TFS 2018

Azure DevOps 扩展能够将用户首选项和复杂的数据结构直接存储在 Microsoft 提供的基础结构上。 这可确保用户的数据安全并像其他组织和项目数据一样进行备份。 这也意味着对于简单的数据存储需求,你 (作为扩展提供程序) 不需要设置或管理 (或支付) 第三方数据存储服务的费用。

可通过两种方式与数据存储服务进行交互:REST API 或作为 VSS SDK 的一部分提供的 Microsoft 提供的客户端服务。 强烈建议扩展开发人员使用提供的客户端服务 API,该 API 通过 REST API 提供方便的包装器。

注意

正在查找 Azure DevOps REST API? 请参阅最新的 Azure DevOps REST API 参考

有关 .NET 客户端库的信息,请参阅 适用于 Azure DevOps 的 .NET 客户端库

可存储的内容

该服务旨在让你存储和管理两种不同类型的数据:

  1. 设置:简单的键值设置 (,如用户首选项)
  2. 文档:类似复杂对象的集合 (文档)

集合是文档的索引容器。 文档是属于集合的 JSON Blob。 除了几个保留属性名称之外,这些文档的架构由你控制和管理。

如何限定数据范围

设置和文档集合的范围可以限定为:

  1. 项目集合:由安装扩展的项目集合的所有用户共享
  2. 用户:安装扩展的项目集合的单个用户

设置存储

用于与设置交互的两个主要函数是 getValue () 和 setValue () :

  • getValue() 采用字符串键 (和其他选项,例如范围) 并返回 IPromise。 此承诺的解决方法包含提供的密钥的值。
  • setValue() 获取字符串键和值 (和其他选项,例如范围) 并返回 IPromise。 此承诺的解决方案包含设置的新值。

下面是如何设置值的示例:

    // Get data service
    VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
        // Set value in user scope
        dataService.setValue("userScopedKey", 12345, {scopeType: "User"}).then(function(value) {
            console.log("User scoped key value is " + value);
        });
    });

下面是如何检索设置值的示例:

    // Get data service
    VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
        // Get value in user scope
        dataService.getValue("userScopedKey", {scopeType: "User"}).then(function(value) {
            console.log("User scoped key value is " + value);
        });
    });

如果未 scopeType 指定,则设置存储在项目集合级别,并且可使用扩展访问该项目集合中的所有用户。 下面是如何在项目集合级别设置设置值的示例:

    // Get data service
    VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
        // Set value (default is project collection scope)
        dataService.setValue("someKey", "abcd-efgh").then(function(value) {
            console.log("Key value is " + value);
        });
    });

) 存储的文档的数据 (集合

若要与键值对以外的更丰富的数据交互,可以使用文档的概念对其扩展的数据执行 CRUD 操作。 文档是一个 JSON Blob,它用两个特殊属性进行扩充:ID 和 __etag。 如果 ID 是扩展的数据模型很重要,或者系统生成未定义 ID,则可以用户定义 ID。 这些 ID 在特定集合中必须是唯一的。 由于集合引用了扩展的特定范围和实例,这意味着可以在不同的集合中使用相同的文档 ID。

以下文档操作可用:

  • 获取文档
  • 创建文档
  • 设置文档 (创建或更新)
  • 更新文档
  • 删除文档

还可以对集合执行单个操作:

  • 获取所有文档

按 ID 获取文档

通过集合中的标识符检索文档非常简单:

    // Get data service
    VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
        // Get document by id
        dataService.getDocument("MyCollection", "MyDocumentId").then(function(doc) {
            // Assuming document has a property named foo
            console.log("Doc foo: " + doc.foo); 
        });
    });

此调用尝试从集合“MyCollection”检索 ID 为“MyDocumentId”的文档。 由于未提供任何范围,因此服务使用的集合的范围限定为此扩展的整个实例的默认值。 如果此集合不存在或该 ID 不存在的文档,则返回 404,该扩展应处理。 返回的文档是包含其所有属性的 JSON 对象,以及数据存储服务使用的特殊 ID 和 __etag 属性。

创建文档

若要创建新文档,请执行如下调用:

    // Get data service
    VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
        // Prepare document first
        var newDoc = {
            fullScreen: false,
            screenWidth: 500
        };
        
        dataService.createDocument("MyCollection", newDoc).then(function(doc) {
            // Even if no ID was passed to createDocument, one gets generated
            console.log("Doc id: " + doc.id);
        });
    });

如果提供的名称和作用域的集合尚不存在,则会在创建文档本身之前动态创建该集合。

如果提供的文档包含属性,该值将用作文档的唯一 id ID。 如果该字段不存在,则由服务生成 GUID,并在解析承诺时返回的文档中包含该 GUID。

如果集合中的另一个文档已存在与文档上提供的文档相同的 ID,则操作将失败。 如果所需的行为是创建新文档(如果 ID 不存在),但修改现有文档(如果存在), setDocument() 则应使用该方法。

设置文档 (更新或创建)

setDocument() 执行等效的“upsert”操作 - 如果集合中提供的文档中存在 ID,则修改现有文档。 如果 ID 不存在,或未提供 ID,则新文档将添加到集合中。

    // Get data service
    VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
        // Prepare document first
        var myDoc = {
            id: 1,
            fullScreen: false,
            screenWidth: 500
        };
        
        dataService.setDocument("MyCollection", myDoc).then(function(doc) {
            console.log("Doc id: " + doc.id);
        });
    });

更新文档

updateDocument 要求正在修改的文档已存在于集合中。 如果未提供 ID,或者文档中提供的 ID 在集合中不存在,则会引发异常。

下面是如何使用更新的示例:

    // Get data service
    VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
        var collection = "MyCollection";
        var docId = "1234-4567-8910";
        // Get document first
        dataService.getDocument(collection, docId, { scopeType: "User" }).then(function(doc) {
            // Update the document
            doc.name = "John Doe";
            dataService.updateDocument(collection, doc, { scopeType: "User" }).then(function(d) {
                // Check the new version
                console.log("Doc version: " + d.__etag);
            });
        });
    });

删除文档

此函数从提供的集合中删除具有提供的 ID 的文档。 如果集合不存在或文档不存在,则返回 404。

下面是示例用法:

    // Get data service
    VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
        var docId = "1234-4567-8910";
        // Delete document
        dataService.deleteDocument("MyCollection", docId).then(function() {
            console.log("Doc deleted");
        });
    });

获取集合中的所有文档

除了对文档本身的操作外,数据存储服务还对集合提供单个操作 - 检索单个集合中的所有文档。 此调用如下所示:

    // Get data service
    VSS.getService(VSS.ServiceIds.ExtensionData).then(function(dataService) {
        // Get all document under the collection
        dataService.getDocuments("MyCollection").then(function(docs) {
            console.log("There are " + docs.length + " in the collection.");
        });
    });

此调用返回范围集合中的所有文档。 如果集合不存在,则返回 404。

高级

如何存储设置

此调用包装 setDocument 客户端方法,并传递几条信息。 如前所述,设置在内部存储为文档,因此在动态中创建一个简单的文档,其中文档的 ID 是 setValue () 方法中提供的键。 文档中还有两个附加属性。 一个是“value”,其中包含传递给该方法的值。 另一个是“revision”,设置为 -1。 修订属性在“使用文档”部分中进行了更详细地讨论,但在设置上下文中通过传递修订:-1 在文档中,我们表示我们并不关心此设置文档的版本控制。

由于设置存储为文档,因此我们需要提供集合名称,指示存储文档的位置。 为简单起见,使用 setValue () /getValue () 方法时,集合名称始终是特殊名称“$settings”。 前面的调用在以下终结点发出 PUT 请求:

GET _apis/ExtensionManagement/InstalledExtensions/{publisherName}/{extensionName}/Data/Scopes/User/Me/Collections/%24settings/Documents

请求有效负载如下:

{
                "id": "myKey",
                "__etag": -1,
                "value": "myValue"
}

REST API

假设在设置值后执行此代码段,应会看到包含文本“Value is myValue”的警报消息。 getValue 方法再次是 REST API 的包装器,向以下终结点发出 GET 请求:

GET _apis/ExtensionManagement/InstalledExtensions/{publisherName}/{extensionName}/Data/Scopes/User/Me/Collections/%24settings/Documents/myKey

etags

字段 __etag 是数据存储服务处理文档并发的方式。 在服务存储对文档的更新之前,将执行检查以验证当前存储的文档是否 __etag 等于 __etag 作为更新的一部分传递的文档。 如果此检查成功,则会 __etag 递增,并将新文档返回到调用方。 如果此检查失败,则表示尝试更新的文档已过期,并引发异常。 扩展编写器可以正常处理此异常,方法是获取最新 __etag 文档合并更改和重新尝试更新,或向用户弹出消息。

某些类型的文档可能不需要这种并发级别,最后一个双赢模型是实际需要的内容。 对于这些情况,编辑文档时,传递 -1 作为 __etag 值,以指示这种类型的功能。 前面所述的设置服务使用此模型保存设置和首选项。