1

我正在使用 C# 驱动程序(来自 NuGet 的 v1.8.3),并且很难确定$addtoSet/upsert操作是否实际上将新项目添加到给定数组中,或者该项目是否已经存在。

添加新项目可能分为两种情况,要么文档根本不存在并且只是由 upsert 创建,要么文档存在但数组不存在或不包含给定项目。

我需要这样做的原因是,我有大量数据要加载到 MongoDB 中,这些数据可能(不应该,但可能)在处理过程中中断。如果发生这种情况,我需要能够从头开始备份,而无需进行重复的下游处理(保持处理幂等)。在我的流程中,如果确定一个项目是新添加的,我将对该给定项目的下游处理排队,如果确定已经在文档中添加,则不需要更多的下游工作。我的问题是结果总是返回说调用修改了一个文档,即使该项目已经存在于数组中并且实际上没有任何修改。

根据我对 C# 驱动程序 api 的理解,我应该能够使用 进行调用WriteConcern.Acknowledged,然后检查WriteConcernResult.DocumentsAffected它是否确实更新了文档。

我的问题是,在所有情况下,写关注结果都返回了 1 个文档已更新。:/

这是我的代码正在调用的示例文档$addToSet,它可能会或可能不会在“项目”列表中包含此特定项目以开始:

{
    "_id" : "some-id-that-we-know-wont-change",
    "items" : [ 
        {                
            "s" : 4,
            "i" : "some-value-we-know-is-static",
        }
    ]
}

我的查询始终使用_id基于处理元数据已知的值:

var query = new QueryDocument
{
     {"_id", "some-id-that-we-know-wont-change"}                       
};

我的更新如下:

var result = mongoCollection.Update(query, new UpdateDocument()
{
     {                                                
          "$addToSet", new BsonDocument()
               {
                    { "items", new BsonDocument()
                         {
                              { "s", 4 },
                              { "i", "some-value-we-know-is-static" }                                                                            
                          } 
                    }
               }
     }
}, new MongoUpdateOptions() { Flags = UpdateFlags.Upsert, WriteConcern = WriteConcern.Acknowledged }); 

if(result.DocumentsAffected > 0 || result.UpdatedExisting)
{
     //DO SOME POST PROCESSING WORK THAT SHOULD ONLY HAPPEN ONCE PER ITEM                                                
}

如果我在一个空集合上运行此代码一次,则会添加文档并且响应符合预期(DocumentsAffected = 1UpdatedExisting = false)。如果我再次运行它(任意次数),文档似乎没有更新,因为它保持不变,但结果现在出乎意料(DocumentsAffected = 1, UpdatedExisting = true)。

DocumentsAffected = 0如果文档未更改,这不应该返回吗?

由于我们每天需要进行数百万次这样的调用,我很犹豫是否将此逻辑转换为每个项目的多个调用(首先检查项目是否存在于给定的文档数组中,然后添加/排队或只是跳过)如果在一切皆有可能。

有没有办法让这个工作在一个单一的电话?

4

1 回答 1

1

当然,您在这里所做的实际上是检查响应,该响应确实表明文档是否已更新或插入,或者实际上是否未发生任何操作。这是您执行更新的最佳指标,$addToSet然后文档将被更新。

$addToSet算子本身不能产生重复,这是算子的本性。但是您的逻辑可能确实存在一些问题:

{                                                
      "$addToSet", new BsonDocument()
           {
                { "items", new BsonDocument()
                     {
                          { "id", item.Id },
                          { "v", item.Value } 
                     }
                }
           }
 }

很明显,您正在展示您的“集合”中的一个项目由两个字段组成,因此如果该内容以任何方式变化(即相同的 id 但不同的值),那么该项目实际上是该集合的“唯一”成员并且将被添加。例如,$addToSet操作员不可能不纯粹基于“id”作为唯一标识符来添加新值。你必须在代码中实际滚动它。

此处重复形式的第二种可能性是您的查询部分未正确找到必须更新的文档。这样做的结果将是创建一个仅包含“集合”中新指定的成员的新文档。所以一个常见的使用错误是这样的:

db.collection.update(
    { 
        "id": ABC,
        "items": { "$elemMatch": {
            "id": 123, "v": 10
         }},
    {
        "$addToSet": {
            "items": {
                "id": 123, "v": 10
            }
        }
    },
    { "upsert": true }
)

这种操作的结果总是会创建一个新文档,因为现有文档不包含“集合”中的指定元素。正确的实现是检查“set”成员的存在并允许$addToSet做这项工作。

如果确实在子文档的所有元素完全相同的“集合”中确实出现了真正的重复条目,那么它是由存在或过去的某些其他代码引起的。

如果您确定正在创建一个新条目,请查看代码中的实例$push或实际上以及似乎作用于同一字段的代码中的数组操作。

但是,如果您正确使用了运算符,那么就会$addToSet完全按照它的意图去做。

于 2014-04-10T00:26:08.793 回答