Azure Functions中Cosmos DB触发器如何获取新旧文档?
你遇到的这个问题很常见——默认的Cosmos DB触发器确实只提供更新后的文档实例。不过有两种可靠的方法可以让你同时获取新旧版本的文档,下面分别说明:
方案一:使用预触发器保存旧文档到辅助集合
这个方法的核心是利用Cosmos DB的预更新触发器,在文档被更新前自动把旧版本存到一个辅助集合里,之后你的Function就可以通过文档ID从这个集合中读取旧版本。
步骤1:创建辅助集合
先在你的Cosmos DB数据库里创建一个辅助集合(比如命名为ItemHistory),用来存储文档的历史版本。可以直接在Azure门户操作,或者用CLI/SDK创建。
步骤2:创建预更新触发器
在你的目标集合Items中创建一个预更新触发器,触发器类型选择Pre,操作选Update。触发器的JavaScript代码如下:
function preUpdate() { var context = getContext(); var request = context.getRequest(); var oldDocument = request.getBody(); // 获取更新前的旧文档 var collection = context.getCollection(); var collectionLink = collection.getSelfLink(); // 把旧文档插入到辅助集合ItemHistory var createDocumentAccepted = collection.createDocument( collectionLink.replace("Items", "ItemHistory"), oldDocument, function(err) { if (err) throw new Error("Failed to save old document: " + err.message); } ); if (!createDocumentAccepted) { throw new Error("Create document request was not accepted."); } }
创建触发器后,记得在更新文档时指定触发这个触发器(如果是通过SDK操作,需要在请求中包含触发器名称)。
步骤3:修改Azure Function代码和绑定
首先,在你的函数绑定配置中添加一个Cosmos DB输入绑定,用来查询辅助集合中的旧文档:
{ "bindings": [ { "type": "cosmosDBTrigger", "name": "documents", "direction": "in", "leaseCollectionName": "leases", "connectionStringSetting": "AzureWebJobsCosmosDBConnectionString", "databaseName": "ToDoList", "collectionName": "Items", "createLeaseCollectionIfNotExists": true }, { "type": "cosmosDB", "name": "oldDocument", "direction": "in", "databaseName": "ToDoList", "collectionName": "ItemHistory", "id": "{documents[0].id}", "connectionStringSetting": "AzureWebJobsCosmosDBConnectionString" } ], "disabled": false }
然后修改你的Function代码,就可以同时拿到新旧文档了:
module.exports = async function (context, documents, oldDocument) { if (!!documents && documents.length > 0) { const newDoc = documents[0]; context.log('Updated Document Id: ', newDoc.id); context.log('New Document: ', newDoc); context.log('Old Document: ', oldDocument); // 这里可以对比新旧文档的属性差异 } context.done(); };
方案二:启用Cosmos DB版本保留并读取旧版本
如果你不想维护辅助集合,可以启用Cosmos DB容器的版本保留功能,这样Cosmos DB会自动保留文档的历史版本,你可以通过文档的版本ID或者时间戳来读取旧版本。
步骤1:启用版本保留
在Azure门户中找到你的Items集合,进入数据资源管理器,打开集合的设置,找到版本保留选项,设置保留期限(比如7天)并保存。也可以用Azure CLI命令启用:
az cosmosdb sql container update --account-name <your-account> --resource-group <your-rg> --database-name ToDoList --name Items --enable-versioning true --max-version-item-count 10
步骤2:在Function中读取旧版本
你需要在Function中使用Cosmos DB SDK来读取旧版本的文档,修改后的代码如下:
const { CosmosClient } = require("@azure/cosmos"); module.exports = async function (context, documents) { if (!!documents && documents.length > 0) { const newDoc = documents[0]; context.log('Updated Document Id: ', newDoc.id); context.log('New Document: ', newDoc); // 初始化Cosmos客户端 const client = new CosmosClient(process.env.AzureWebJobsCosmosDBConnectionString); const database = client.database("ToDoList"); const container = database.container("Items"); // 读取旧版本文档(这里读取上一个版本,你可以根据需要调整版本号) try { const oldDocResponse = await container.item(newDoc.id, newDoc._partitionKey).read({ accessCondition: { type: "IfNotModified", condition: newDoc._etag // 使用新文档的ETag来获取旧版本 }, maxItemCount: -1, enableCrossPartitionQuery: true }); context.log('Old Document: ', oldDocResponse.resource); } catch (err) { if (err.code === 412) { context.log('No older version found for document: ', newDoc.id); } else { context.error('Error fetching old document: ', err); } } } context.done(); };
注意:这种方式的旧版本有保留期限,超过期限后会被自动清理,适合不需要长期保留旧文档的场景。
内容的提问来源于stack exchange,提问作者Tushar patel

