在使用 C# 替换其他字段的同时向 MongoDb 中的现有字典添加键

Add a key to an existing dictionary in MongoDb while replacing other fields using C#

我想通过修改现有文档中的字典的新值来更新现有文档,同时替换其他字段。我们可以把它看作是改变了当前地址,同时保留了以前行踪的列表。

构成信息的class是这样的

class Thing
{
  public string Name { get; set; }
  public string Location { get; set; }
  public Dictionary<string, string> History { get; set; }
}

我正在使用以下代码进行更新。

Thing update = ...;

Thing output = await _dbContext.Things
    .FindOneAndUpdateAsync(
        Builders<Thing>.Filter.Where(a => a.Name == update.name),
        Builders<Thing>.Update.Set(
            b => b,
            update
        ), ...
    );

显然,我需要向数据库解释应该如何进行更新:将 Location 的当前值添加到 History,例如时间戳或索引作为键)。谷歌搜索给了我 this blog 建议对 ReturnDocument 的改变,就像这样。

Thing output = await _dbContext.Things
    .FindOneAndUpdateAsync(
        Builders<Thing>.Filter.Where(a => a.Name == update.name),
        Builders<Thing>.Update.Set(
            b => b,
            update
        ),
        new FindOneAndUpdateOptions<Thing>
        {
            ReturnDocument = ReturnDocument.Before
        }, ...
    );

现在,我想(尽管不确定)我应该将当前值添加到历史记录中。但是,我什至无法尝试,因为我无法访问文档的 Thing 类型内容。我只能获得一个 BSON 对象,这给了我相当少的东西。我还发现 this answer but it replaces the current value of a key in the dictionary instead of amending an additional one (also, I have a slight preference for lambda expressions if such is a viable option). The usual check at the official docs 只会导致更多的混乱。以下伪代码显示了我想要达到的目标。

...,
Builders<Thing>.Update.Set(
  b => b,
  new Thing
  {
    Name = b.name,
    Location = update.location,
    History = History.Add(DateTime.Now, b.Location)
  }
), ...

我该怎么办?

首先,您需要将字典 key/value 对存储为数据库中具有以下属性的文档数组:

[BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
public Dictionary<string, string> History { get; set; }

然后您需要 运行 使用类似以下查询的聚合管道更新:

db.Thing.updateOne(
    { Name: "NAME" },
    [
        {
            $set: { Location: "NEW LOC" }
        },
        {
            $set: {
                History: {
                    $concatArrays: [
                        "$History",
                        [{ k: "NEW DATE", v: "$Location" }]
                    ]
                }
            }
        }
    ])

不幸的是,c# 驱动程序没有对管道更新的强类型支持,也没有像 $concatArray 这样的运算符。所以你必须求助于像这样的基于字符串的解决方案:

var filter = Builders<Thing>.Filter.Where(t => t.Name == "NAME");

var pipeline = PipelineDefinition<Thing, Thing>.Create(@"
{
    $set: {
        History: {
            $concatArrays: [
                '$History',
                [{ k: 'NEW DATE', v: '$Location' }]
            ]
        }
    }
}");

await collection.UpdateOneAsync(filter, pipeline);

查看 this article 以获取针对 运行 像这样的高级查询的替代解决方案。