如何确定$ addToSet是否实际将新项添加到MongoDB文档中,或者该项是否已存在?

时间:2014-04-09 19:07:47

标签: c# mongodb idempotent

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

添加一个新项目可能会分为两种情况,要么文档根本不存在,只是由upsert创建,或者文档存在但数组不存在或没有? t包含给定的项目。

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

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

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

以下是我的代码正在调用$addToSet的示例文档,该文档可能会也可能不会在" items"中包含此特定项目。列表开头:

{
    "_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吗?

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

有没有办法在一次通话中使用它?

1 个答案:

答案 0 :(得分:1)

当然,您在这里所做的实际上是检查响应,该响应指示文档是否已更新或插入,或者实际上是否发生了操作。对于执行更新的 $addToSet ,这是您的最佳指标,然后文档会更新。

$addToSet 运算符本身无法生成重复项,即运算符的性质。但是你的逻辑可能确实存在一些问题:

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

很明显,你正在显示你的" set"中的一个项目。由两个字段组成,所以如果该内容以任何方式变化(即相同的id但不同的值),那么该项目实际上是一个"唯一的"集合的成员,将被添加。例如, $addToSet 运算符无法完全基于" id"添加新值。作为唯一标识符。你必须在代码中实际滚动它。

复制形式的第二种可能性是您的查询部分未正确查找必须更新的文档。这样做的结果是创建一个新文档,其中只包含" set"中新指定的成员。因此常见的使用错误是这样的:

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

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

如果确实你确实在" set"中出现了 true 重复的条目。在子文档的所有元素完全相同的情况下,它是由某些其他代码引起的或者过去引起的。

如果您确定要创建新条目,请查看 $push 实例的代码,或者确实在代码中执行数组操作,这些代码似乎在同一个字段上执行操作。

但是,如果您正确使用了运算符,那么 $addToSet 就可以完成它的目的。

相关问题