You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用UpsertItemAsync时如何存储首次插入的时间戳?

无需预检查实现首次插入时间戳的方案

针对你在C#开发中遇到的需求——给存储项添加首次插入时间戳,且不想提前调用ReadItem检查项是否存在,以下是两种实用的实现方式(以Azure Cosmos DB为例,其他数据库可类比调整):

方法1:用Patch API实现增量更新(推荐)

利用Cosmos DB的Patch API,我们可以只在CreatedAt字段不存在时才设置它,一次请求完成Upsert操作,完全不需要预读取:

  1. 先定义实体类,给CreatedAt一个默认值(仅首次插入时可能用到):
public class BusinessEntity
{
    public string Id { get; set; }
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    // 其他业务属性,比如Name、Status等
}
  1. 执行Patch操作时,指定仅当CreatedAt不存在时才赋值,同时可同步更新其他字段:
var container = _cosmosClient.GetContainer("YourDb", "YourContainer");

var patchOps = new List<PatchOperation>
{
    // 核心逻辑:仅当CreatedAt字段不存在时,设置为当前UTC时间
    PatchOperation.AddIfNotExists("/CreatedAt", DateTime.UtcNow),
    // 其他需要更新的字段,比如修改Name
    PatchOperation.Set("/Name", "UpdatedName")
};

// 执行Patch Upsert,无需提前检查项是否存在
var response = await container.PatchItemAsync<BusinessEntity>(
    id: targetId,
    partitionKey: new PartitionKey(targetPartitionKey),
    patchOperations: patchOps);

这种方式的好处:

  • 单请求完成操作,性能远优于"读+写"两次请求
  • 天然保证CreatedAt只在首次插入时被设置,后续更新绝不会覆盖
  • 避免并发场景下的竞态问题

方法2:用数据库存储过程封装逻辑

如果需要更复杂的业务判断,比如插入时要额外处理某些规则,可以把逻辑封装在数据库端的存储过程中,客户端直接调用即可:

  1. 编写Cosmos DB存储过程(JavaScript):
function upsertWithCreatedAt(item) {
    const collection = getContext().getCollection();
    const docLink = collection.getSelfLink() + "/docs/" + item.id;

    // 尝试读取目标文档
    collection.readDocument(docLink, (err, existingDoc) => {
        if (err) {
            // 文档不存在,插入并设置CreatedAt
            item.CreatedAt = new Date();
            collection.createDocument(collection.getSelfLink(), item, (createErr, newDoc) => {
                if (createErr) throw createErr;
                getContext().getResponse().setBody(newDoc);
            });
        } else {
            // 文档存在,仅更新传入的业务字段,保留原CreatedAt
            delete item.CreatedAt;
            Object.assign(existingDoc, item);
            collection.replaceDocument(existingDoc._self, existingDoc, (replaceErr, updatedDoc) => {
                if (replaceErr) throw replaceErr;
                getContext().getResponse().setBody(updatedDoc);
            });
        }
    });
}
  1. C#代码中调用存储过程:
var container = _cosmosClient.GetContainer("YourDb", "YourContainer");
var targetItem = new BusinessEntity { Id = "target-id", Name = "NewName" };

var result = await container.Scripts.ExecuteStoredProcedureAsync<BusinessEntity>(
    storedProcedureId: "upsertWithCreatedAt",
    partitionKey: new PartitionKey(targetItem.PartitionKey),
    parameters: new[] { targetItem });

其他数据库适配提示

如果用的是SQL Server这类关系型数据库,可以用MERGE语句实现类似逻辑:

MERGE INTO YourTable t
USING (VALUES (@Id, @Name, GETUTCDATE())) s(Id, Name, CreatedAt)
ON t.Id = s.Id
WHEN NOT MATCHED THEN
    INSERT (Id, Name, CreatedAt) VALUES (s.Id, s.Name, s.CreatedAt)
WHEN MATCHED THEN
    UPDATE SET t.Name = s.Name; -- 只更新业务字段,不碰CreatedAt

内容的提问来源于stack exchange,提问作者MaxP

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 22:55:34