【问题标题】:mongoDB upsert on array数组上的mongoDB upsert
【发布时间】:2014-05-05 02:09:25
【问题描述】:

我的数据库模型看起来像这样

"clientId":"123456"
"devices" : 
[{
      "deviceId" : "123",
      "deviceType" : "ios",
      "notification" : true,
  }
  {
      "deviceId" : "321",
      "deviceType" : "android",
      "notification" : true,

  }
 ]

我传递给我的 DB 方法的 c# 模型有 ClientId、DeviceId、DeviceType、notification。请注意,它没有设备数组

我想要实现的行为

  1. (工作中)如果ClientId还没有在数据库中,使用传递的值在db中创建一个新记录,(设备数组将有一个元素)

  2. 如果已经在数据库中找到ClientId,则按照以下规则更新记录:

    2.1 如果设备数组不包含 DeviceId,则使用传递的值向数组中添加一个新元素

    2.2 如果 devices 数组中已经包含 DeviceId,则更新元素的 deviceType 和通知。

我正在使用 c#。任何帮助表示赞赏

例子:给定上面的数据库状态,通过传递clientId: 123456, deviceId 321 deviceType "kindle", notification "false" db会变成

"clientId":"123456"
"devices" : 
[{
      "deviceId" : "123",
      "deviceType" : "ios",
      "notification" : true,
  }
  {
      "deviceId" : "321",
      "deviceType" : "kindle",
      "notification" : false,

  }
 ]

【问题讨论】:

  • 好问题。提出一个 JIRA。我可以在您的问题中添加一些内容以澄清问题吗?
  • 您是否删除了更新?我不太明白你在说什么,现在它消失了..
  • 是的,因为我认为它实际上需要另一个问题。具体情况我问了here

标签: c# arrays mongodb mongodb-query


【解决方案1】:

这并不像您想象的那么简单,实际上有趣的是,您将对此的分析分为三个部分。因为,你猜怎么着?这正是您必须做的。让我们考虑一下步骤:

1。如果文档不存在则插入文档

db.collection.update(
    { 
        "clientId":"123456"
    },
    {
        "$setOnInsert": {
            "clientId": "123456",
            "devices": [{
                "deviceId": "321",
                "deviceType" : "kindle",
                "notification" : false
            }]
        }
    },
    { "upsert": true }
)

所以您要做的是插入一个新文档,其中“clientId”当前不存在。这可以作为“upsert”来完成,以避免可能的唯一键冲突,甚至在没有“唯一”约束的情况下,这样的“upsert”性质可确保您仅在未找到“新”文档时创建它。这里还有$setOnInsert,因为您不想想对此时“找到”的文档做任何事情。

注意这里没有尝试匹配数组中的元素。这是因为您可能不想仅仅因为现有文档没有“那个”数组元素而“创建”一个新文档。这将我们带到了下一步。

2。更新确实存在的文档的内容

db.collection.update(
    { 
        "clientId":"123456",
        "devices": { "$elemMatch": { "deviceId" : "321" } }
    },
    {
        "$set": {
            "devices.$.deviceType" : "kindle",
            "devices.$.notification" : false
        }
    }
)

现在您想实际尝试“匹配”文档中的“clientId”,确实包含数组中的一个元素,该元素也与您正在寻找的“deviceId”匹配。因此,通过指定要匹配的条件,您可以使用位置 $ 运算符来将字段设置在“匹配”位置。

如上所述,这将匹配 onenothing,因此更新完成或未完成。因此,这里转到我们级联的最后一部分:

3。在不存在的地方添加数组元素

db.collection.update(
    { 
        "clientId":"123456"
    },
    {
        "$addToset": { "devices": {
            "deviceId" : "321",
            "deviceType" : "kindle",
            "notification" : false
        }}
    }
)

所以这是重要的最后阶段。原因是如果前面的操作确实“创建”或“更新”现有文档,那么这里使用$addToSet确定你不是“将另一个文档推送到具有相同“deviceId”但其他值不同的数组。如果这些阶段中的一个有效,那么这将看到该元素的所有值都已经存在,并且不会再添加另一个。

如果您尝试以不同的顺序执行此操作,则在您展示的情况下,您将在数组中有 两个 文档具有相同的“deviceId”,但“deviceType”和“的值不同”通知”。所以这就是为什么它排在最后。

结论

不幸的是,没有简单的方法可以将这些组合为一个操作。运算符根本不存在,因此这可以在单个语句中完成,因此您必须执行 三个更新操作才能执行您想要的操作。同样如上所述,这些更新的应用顺序重要,以便您获得所需的结果。

虽然这在当前的“生产”版本中尚不存在,但即将发布的版本(截至撰写本文时为 2.6 及更高版本)确实可以使用 update 的新语法来处理 "batch" 这些请求:

db.runCommand(
    "update": "collection",
    "updates": [
        { 
            "q": { "clientId":"123456" },
            "u": {
                "$setOnInsert": {
                    "clientId": "123456",
                    "devices": [{
                    "deviceId": "321",
                    "deviceType" : "kindle",
                    "notification" : false
                }]
            },
            "upsert": true
        },
        {
            "q": { 
                 "clientId":"123456",
                 "devices": { "$elemMatch": { "deviceId" : "321" } }
            },
            "u": {
                "$set": {
                    "devices.$.deviceType" : "kindle",
                    "devices.$.notification" : false
                 }
            }
        },
        {
            "q": { "clientId":"123456" },
            "u": {
                "$addToset": { "devices": {
                    "deviceId" : "321",
                    "deviceType" : "kindle",
                    "notification" : false
                }}
            }
        }
    ]
)

所以虽然这仍然本质上是三个操作,但至少你可以通过网络发送它们一次

【讨论】:

  • 哇,多么详细和结构化的回应。感谢您花时间写这篇文章!
  • 所以,问是因为它是 OP 上的标签之一,但是您是否偶然知道如何使用 C# 驱动程序实现此目的?
【解决方案2】:

您正在寻找一个典型的位置运算符案例:

db.coll.update
(
    {
        "clientId":"123456","devices.deviceId":"321"
    },
    {
        $set:
        {
            "devices.$.deviceType":"kindle","devices.$.notification":"false"
        }
    }
)

您需要更新在数组中匹配的元素,$ 会为您执行此操作,因此您可以在数组中找到包含特定元素的文档,并使用位置运算符 $“记住”您匹配哪个以更新它.

【讨论】:

  • 谢谢。如果它已经包含 deviceId,则会正确更新它,如果元素尚不存在,是否有办法将其概括为插入新元素?
  • 不幸的是,在这种情况下,由于位置运算符是一个非常特殊的情况,并且 upsert 不起作用,因为它是一个数组。我一直在想pop和push可能有一个技巧,但它太复杂了。我会检查 upsert 是否更新了一条记录,以防它没有我会做一个插入,而不是总是搜索记录(至少你减少了操作的百分比)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多