简体   繁体   中英

In C#, using the Mongo Driver, how can I set a "last modified field"?

Because Personio's API won't tell me when employees are created or modified which creates scalability issues for us, I'm creating a cache where I can store the Personio with metadata including whether/when it has been modified.

I've already learned from @Charchit Kapoor I should be able to use $addFields with $function to populate a "lastModified" value, so now I'm trying to make this work within my C# application.

I've created the following Save method:

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;
                personioRecord.SyncTimestamp = DateTime.UtcNow;

                return collection.UpdateOneAsync(
                    filter: Builders<StoredDynamicRecord>.Filter.Eq(x => x.CacheId, personioRecord.CacheId),

                    update: new EmptyPipelineDefinition<StoredDynamicRecord>()
                        .AppendStage<StoredDynamicRecord, StoredDynamicRecord, StoredDynamicRecord>(
                            @$"{{ ""$replaceWith"": 
                                {personioRecord.ToBsonDocument()} 
                            }}"
                        )

                        .AppendStage<StoredDynamicRecord, StoredDynamicRecord, StoredDynamicRecord>(
                            @$"{{ ""$addFields"": {{ ""_lastModified"": {{ ""$function"": {{
                                    ""lang"": ""js"",
                                    ""args"": [
                                        ""$ROOT"",
                                        {{
                                            ""key"": 1,
                                            ""data"": ""somedata""
                                        }}
                                    ],
                                    ""body"": ""function(oldDoc, newDoc) {{
                                        return (!oldDoc || JSON.stringify(oldDoc.AttributesByName) !== JSON.stringify(newDoc.AttributesByName))
                                            ? newDoc._syncTimestamp
                                            : oldDoc._lastModified

                                    }}""
                                }} }} }} }}"
                            ),

                    options: new()
                    {
                        IsUpsert = true
                    },

                     cancellationToken
                );
            });

        return Task.WhenAll(updateResultTasks);
    }

However at least one thing is wrong with this since the value of "_lastModified" is always null , when I would expect it never should be. If there is no oldDoc, I would expect the value to be set to newDoc._syncTimestamp which should be the same as personioRecord.SyncTimestamp = DateTime.UtcNow .

If I switch the order of the AppendStage s, the value is "ISODate("0001-01-01T00:00:00.000+0000")" instead of null, which is arguably better but still not what is expected or wanted.

Fwiw, this is the StoredDynamicRecord class:

public class StoredDynamicRecord : DynamicRecord, IStoredRecord
{
    public const string SyncTimestampField = "_syncTimestamp";
    public const string LastModifiedTimestampField = "_lastModified";

    [BsonId]
    public string CacheId { get; set; }

    [BsonElement(SyncTimestampField)]
    public DateTime SyncTimestamp { get; set; }

    [BsonElement(LastModifiedTimestampField)]
    public DateTime LastModified { get; set; }

    public StoredDynamicRecord From(DynamicRecord dynamicRecord) =>
        new()
        {
            Type = dynamicRecord.Type,
            AttributesByName = dynamicRecord.AttributesByName
        };
}

What am I missing or doing wrong?

Couldn't get the above approach to work.

This works, but in memory instead of the database:

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

        List<Task<StoredDynamicRecord>> updateResultTasks = personioRecords.Select(personioRecord =>
        {
            Dictionary<string, DynamicRecordAttribute> attributesByName = personioRecord.AttributesByName;
            string id = (string)attributesByName["id"].Value;
            personioRecord.CacheId = string.IsNullOrEmpty(id)
                                                ? ObjectId.GenerateNewId()
                                                    .ToString()
                                                : id;
            DateTime utcNow = DateTime.UtcNow;
            personioRecord.SyncTimestamp = utcNow;

            StoredDynamicRecord oldRecord = employeeCollection
                .Find(x => x.CacheId == personioRecord.CacheId)
                .FirstOrDefault();

            return employeeCollection.FindOneAndUpdateAsync(
                    filter: Builders<StoredDynamicRecord>.Filter.Eq(x => x.CacheId, personioRecord.CacheId),

                    update: Builders<StoredDynamicRecord>.Update
                        .Set(x => x.CacheId, string.IsNullOrEmpty(id)
                                                ? ObjectId.GenerateNewId()
                                                    .ToString()
                                                : id
                        )
                        .Set(x => x.Type, personioRecord.Type)
                        .Set(x => x.AttributesByName, attributesByName)
                        .Set(x => x.SyncTimestamp, personioRecord.SyncTimestamp)
                        .Set(x => x.LastModified, (!personioRecord.Equals(oldRecord))
                            ? utcNow
                            : oldRecord.LastModified
                        ),

                    options: new FindOneAndUpdateOptions<StoredDynamicRecord>()
                    {
                        IsUpsert = true
                    },

                     cancellationToken
                );
        })
            .ToList();

        return Task.WhenAll(updateResultTasks);
    }

Note, for this to work, it is important to define equality:

    public override bool Equals(object that) =>
        this == that
        || (
            that != null
            && that is StoredDynamicRecord otherRecord
            && CacheId == otherRecord.CacheId
            && AttributesByName.Count == otherRecord.AttributesByName.Count
            && AttributesByName.Keys.ForAll(x =>
                AttributesByName[x].ToString() == otherRecord.AttributesByName[x].ToString()
            )
        );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM