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

The*_*eoK 4 go mongodb 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"`
}
Run Code Online (Sandbox Code Playgroud)

我正在使用指针,以便能够区分缺失值 ( nil) 和默认值(例如字符串、值等)。我也曾经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"
}
Run Code Online (Sandbox Code Playgroud)

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

newFirstName := "New Value"
UserModel{
  BasicInfo: &UserBasicInfoModel{
    FirstName: &newFirstName,
  },
}
Run Code Online (Sandbox Code Playgroud)

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

UpdateId(id, bson.M{"$set": changes})
Run Code Online (Sandbox Code Playgroud)

我得到的回复如下:

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

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

我本来希望得到以下内容:

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

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

icz*_*cza 7

首先让我们快速解释一下你的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"`
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

我们可以做什么?

展平嵌入的文档

一个简单的解决方案是不使用嵌入式文档,而是添加UserBasicInfoModelto字段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"`
}
Run Code Online (Sandbox Code Playgroud)

,inline选项的混合

这个解决方案保留了单独的 Go 结构,但在 MongoDB 中它不会是一个嵌入的文档(BasicInfo会像前面的例子一样被扁平化):

type UserModel struct {
    Id        string             `bson:"_id,omitempty"`
    CreatedAt *time.Time         `bson:"createdAt,omitempty"`
    BasicInfo UserBasicInfoModel `bson:"basicInfo,omitempty,inline"`
}
Run Code Online (Sandbox Code Playgroud)

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

进行“手动”更新

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

c.UpdateId(Id, bson.M{"$set": bson.M{
    "basicInfo.firstName": newFirstName,
}})
Run Code Online (Sandbox Code Playgroud)

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

  • @TheoK 请注意,可以创建一个实用程序函数来为您提供此功能。我估计这可以用大约 50 行代码来完成,如果你想的话,这还不算多。也许对你来说实施这个是值得的。 (3认同)
  • 谢谢你的回答。经过一些研究(关于 Java mongo 驱动程序问题),我发现他们也这么说。也许内联选项最适合我,因为它会使 go 结构更有条理,并且我会将文档展平以执行部分​​更新。虽然这无法完成,但很奇怪。 (2认同)