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

时间:2018-05-26 12:22:52

标签: mongodb go 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在子文档中实现部分更新?

1 个答案:

答案 0 :(得分:2)

首先让我们快速解释您的createdAt字段。这是您保存的值:2018-05-26T15:08:56.764453386+03:00。知道MongoDB以毫秒精度和UTC时区存储日期。因此,从MongoDB保存和检索的这个日期变为2018-05-26T12:08:56.764Z,这是"相同"时刻,仅在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属性的嵌入文档。 这将删除嵌入的lastName文档的basicInfo字段。因此,当您在更新后将文档解组为UserModel类型的值时,{ {1}}字段将保留LastName(因为它不再出现在MongoDB中)。

我们能做什么?

展平嵌入文档

一个简单的解决方案是不使用嵌入式文档,而是将nil字段添加到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"` } 选项混合

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

BasicInfo

请注意,如果使用type UserModel struct { Id string `bson:"_id,omitempty"` CreatedAt *time.Time `bson:"createdAt,omitempty"` BasicInfo UserBasicInfoModel `bson:"basicInfo,omitempty,inline"` } BasicInfo必须是非指针。这不是问题,因为如果不改变它的字段,我们可以将它保留为空结构,因为它的字段是指针,所以让它们,inline不会改变它们。

做"手册"更新

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

mgo

是的,这根本不方便。如果您确实需要多次使用不同类型,可以创建一个使用反射的实用程序函数,递归迭代字段,并从非c.UpdateId(Id, bson.M{"$set": bson.M{ "basicInfo.firstName": newFirstName, }}) 的字段组装更新文档。然后,您可以将动态生成的更新文档传递给nil,例如。