使用 mgo 部分更新 mongoDB 中的嵌入文档

Partial update of embedded document in mongoDB using mgo

我有以下型号:

type UserModel struct {
    Id        string              `bson:"_id,omitempty"`
    CreatedAt *time.Time          `bson:"createdAt,omitempty"`
    BasicInfo *UserBasicInfoModel `bson:"basicInfo,omitempty"`
}

// *Embedded document*
type UserBasicInfoModel struct {
    FirstName    *string `bson:"firstName,omitempty"`
    LastName     *string `bson:"lastName,omitempty"`
}

我正在使用指针,以便能够区分缺失值(nil)和默认值(例如字符串,false 值等)。我还使用 omitempty 来进行部分更新。

当我创建一个用户时,我得到以下(正确的)回复:

"id": "aba19b45-5e84-55e0-84f8-90fad41712f6",
"createdAt": "2018-05-26T15:08:56.764453386+03:00",
"basicInfo": {
    "firstName": "Initial first name",
    "lastName": "Initial last name"
}

当我尝试更新 文档时,虽然遇到了问题。 我将更改作为新 UserModel 发送,只更改嵌入文档中的 FirstName 字段,如下所示:

newFirstName := "New Value"
UserModel{
  BasicInfo: &UserBasicInfoModel{
    FirstName: &newFirstName,
  },
}

我用来执行 更新 的代码如下:

UpdateId(id, bson.M{"$set": changes})

我得到的回复如下:

"id": "aba19b45-5e84-55e0-84f8-90fad41712f6",
"createdAt": "2018-05-26T12:08:56.764Z",
"basicInfo": {
    "firstName": "New Value",
    "lastName": null
}

createdAt 不是 null(如我所料)但是 lastNamenull(这不是我所期望的)

我本希望得到以下信息:

"id": "aba19b45-5e84-55e0-84f8-90fad41712f6",
"createdAt": "2018-05-26T12:08:56.764Z",
"basicInfo": {
    "firstName": "New Value",
    "lastName": "Initial last name"
}

如何使用 mgo 实现子文档的部分更新?

首先让我们快速解释一下您的 createdAt 字段。这是您保存的值:2018-05-26T15:08:56.764453386+03:00。知道 MongoDB 以毫秒精度和 UTC 时区存储日期。因此,从 MongoDB 保存和检索的日期变为 2018-05-26T12:08:56.764Z,这是 "same" 时间点,仅在 UTC 时区,精度被截断为毫秒。

现在开始更新嵌入文档:

简短而不幸的答案是我们不能直接使用 mgo 库和 Go 模型来做到这一点。

为什么?

当我们使用 ,omitempty 选项时,我们将一些指针字段保留为零值(即 nil),就好像我们使用的值的类型不是'甚至没有那些字段。

所以在你的例子中,如果你只改变了BasicInfo.FirstName字段,而你使用这个值来更新,就相当于使用了这些结构:

type UserModel struct {
    Id        string              `bson:"_id,omitempty"`
    BasicInfo *UserBasicInfoModel `bson:"basicInfo,omitempty"`
}

type UserBasicInfoModel struct {
    FirstName    *string `bson:"firstName,omitempty"`
}

因此您发出的 update 命令的效果如下:

db.users.update({_id: "aba19b45-5e84-55e0-84f8-90fad41712f6"},
    {$set:{
        "_id": "aba19b45-5e84-55e0-84f8-90fad41712f6",
        "basicInfo": {
            "firstName": "New Value"
        }
    }}
)

这是什么意思?将 _id 设置为相同的值(不会更改),并将 basicInfo 字段设置为只有一个 firstName 属性 的嵌入文档。 这将擦除嵌入 basicInfo 文档的 lastName 字段。 因此,当您在更新后将文档解组为 UserModel 类型的值时,LastName 字段将保持 nil(因为它不再存在于 MongoDB 中)。

我们能做什么?

拼合嵌入文档

一个简单的解决方案是不使用嵌入式文档,而是将 UserBasicInfoModel 的字段添加到 UserModel:

type UserModel struct {
    Id        string     `bson:"_id,omitempty"`
    CreatedAt *time.Time `bson:"createdAt,omitempty"`
    FirstName *string    `bson:"firstName,omitempty"`
    LastName  *string    `bson:"lastName,omitempty"`
}

具有 ,inline 选项的混合

此解决方案保留了单独的 Go 结构,但在 MongoDB 中它将不是嵌入式文档(BasicInfo 将像前面的示例一样被展平):

type UserModel struct {
    Id        string             `bson:"_id,omitempty"`
    CreatedAt *time.Time         `bson:"createdAt,omitempty"`
    BasicInfo UserBasicInfoModel `bson:"basicInfo,omitempty,inline"`
}

请注意,如果使用 ,inline,则 BasicInfo 必须是非指针。这不是问题,因为如果不更改其字段,我们可以将其保留为空结构,因为它的字段是指针,因此保留它们 nil 不会更改它们。

正在进行 "manual" 更新

如果您确实需要使用嵌入文档,mgo 库允许您更新嵌入文档的特定字段,但是您必须 "manually" 构建更新文档,如本例所示:

c.UpdateId(Id, bson.M{"$set": bson.M{
    "basicInfo.firstName": newFirstName,
}})

是的,这一点都不方便。如果您确实需要多次使用不同的类型,您可以创建一个使用反射的实用函数,递归地迭代字段,并 assemble 从不是 nil 的字段更新文档。然后您可以将动态生成的更新文档传递给 UpdateId() 例如。