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

C#使用Mongo Driver设置Personio缓存lastModified字段异常问题

问题分析与修复

核心问题

  1. 管道顺序与参数传递错误
    先用$replaceWith会直接覆盖旧文档,后续$function中的$ROOT已经是新文档,根本无法获取旧数据;同时$function的第二个参数传入的自定义对象并非实际新文档,逻辑完全错误。

  2. DateTime默认值问题
    C#中DateTime类型的默认值是0001-01-01,当MongoDB映射时如果没有赋值,就会存入这个默认的ISODate。

修复方案

方案1:调整管道顺序,先计算再替换

先基于旧文档计算_lastModified,再合并新数据:

public Task<UpdateResult[]> Save(List<StoredDynamicRecord> personioRecords, CancellationToken cancellationToken)
{
    IMongoCollection<StoredDynamicRecord> collection = GetCollection<StoredDynamicRecord>();

    IEnumerable<Task<UpdateResult>> updateResultTasks
        = personioRecords.Select(personioRecord =>
        {
            string id = (string)personioRecord.AttributesByName["id"].Value;
            personioRecord.CacheId = string.IsNullOrEmpty(id)
                                    ? ObjectId.GenerateNewId().ToString()
                                    : id;
            var syncTimestamp = DateTime.UtcNow;
            personioRecord.SyncTimestamp = syncTimestamp;
            var newDocBson = personioRecord.ToBsonDocument();

            return collection.UpdateOneAsync(
                filter: Builders<StoredDynamicRecord>.Filter.Eq(x => x.CacheId, personioRecord.CacheId),
                update: new EmptyPipelineDefinition<StoredDynamicRecord>()
                    // 先计算_lastModified:新增用syncTimestamp,更新且数据变化也用syncTimestamp,否则保留旧值
                    .AppendStage(@"{
                        $addFields: {
                            _lastModified: {
                                $function: {
                                    lang: ""js"",
                                    args: [""$$ROOT"", " + BsonValue.Create(syncTimestamp).ToJson() + @"],
                                    body: ""function(oldDoc, newSyncTime) {
                                        if (!oldDoc) return newSyncTime;
                                        const oldAttrs = oldDoc.AttributesByName;
                                        const newAttrs = " + newDocBson["AttributesByName"].ToJson() + @";
                                        return JSON.stringify(oldAttrs) !== JSON.stringify(newAttrs) ? newSyncTime : oldDoc._lastModified;
                                    }""
                                }
                            }
                        }
                    }")
                    // 合并新文档与计算好的_lastModified
                    .AppendStage(@"{
                        $replaceWith: {
                            $mergeObjects: [ " + newDocBson.ToJson() + @", { _lastModified: ""$_lastModified"" } ]
                        }
                    }"),
                options: new UpdateOptions { IsUpsert = true },
                cancellationToken
            );
        });

    return Task.WhenAll(updateResultTasks);
}

方案2:用MongoDB原生操作符替代JS函数(性能更优)

避免使用JavaScript函数,改用原生聚合操作符实现逻辑:

public Task<UpdateResult[]> Save(List<StoredDynamicRecord> personioRecords, CancellationToken cancellationToken)
{
    IMongoCollection<StoredDynamicRecord> collection = GetCollection<StoredDynamicRecord>();

    IEnumerable<Task<UpdateResult>> updateResultTasks
        = personioRecords.Select(personioRecord =>
        {
            string id = (string)personioRecord.AttributesByName["id"].Value;
            personioRecord.CacheId = string.IsNullOrEmpty(id)
                                    ? ObjectId.GenerateNewId().ToString()
                                    : id;
            var syncTimestamp = DateTime.UtcNow;
            personioRecord.SyncTimestamp = syncTimestamp;
            var newAttrs = personioRecord.AttributesByName;

            var updateDefinition = Builders<StoredDynamicRecord>.Update
                .Set(x => x.SyncTimestamp, syncTimestamp)
                .Set(x => x.AttributesByName, newAttrs)
                // 用$cond判断:新增或属性变化时设置为syncTimestamp,否则保留原值
                .Set(x => x.LastModified,
                    new BsonDocument("$cond", new BsonArray
                    {
                        new BsonDocument("$or", new BsonArray
                        {
                            new BsonDocument("$eq", new BsonArray { "$$ROOT", BsonNull.Value }), // 新增文档
                            new BsonDocument("$ne", new BsonArray { "$$ROOT.AttributesByName", BsonValue.Create(newAttrs) }) // 属性变化
                        }),
                        syncTimestamp,
                        "$$ROOT._lastModified"
                    }));

            return collection.UpdateOneAsync(
                filter: Builders<StoredDynamicRecord>.Filter.Eq(x => x.CacheId, personioRecord.CacheId),
                update: updateDefinition,
                options: new UpdateOptions { IsUpsert = true },
                cancellationToken
            );
        });

    return Task.WhenAll(updateResultTasks);
}

额外注意事项

  • 确保AttributesByName能被MongoDB正确序列化/反序列化,否则对比会失效;
  • 大型文档推荐用方案2,原生操作符性能远优于JS函数;
  • 可以给LastModified设置默认值:public DateTime LastModified { get; set; } = DateTime.UtcNow;,避免出现默认的0001-01-01值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 00:46:09