如何确定$addToSet是否真的向MongoDB文档中添加了一个新项目,或者该项目是否已经存在

本文关键字:是否 新项目 一个 或者 存在 项目 真的 addToSet 何确定 MongoDB 添加 | 更新日期: 2023-09-27 17:57:34

我正在使用C#驱动程序(NuGet的v1.8.3),很难确定$addtoSet/upsert操作是否真的向给定数组中添加了一个NEW项,或者该项是否已经存在。

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

我需要这样做的原因是,我有大量数据要加载到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 = 1UpdatedExisting = true)。

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

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

有什么方法可以在一个电话中实现这一点吗?

如何确定$addToSet是否真的向MongoDB文档中添加了一个新项目,或者该项目是否已经存在

当然,您在这里所做的实际上是检查响应,它确实指示文档是否被更新或插入,或者实际上是否两个操作都没有发生。这是您的最佳指标,因为$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 }
)

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

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

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

但是,如果您正确地使用了运算符,则$addToSet将执行它想要执行的操作。