Частичное обновление встроенного документа в mongoDB с использованием mgo - PullRequest
0 голосов
/ 26 мая 2018

У меня есть следующая модель:

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) и значение по умолчанию (например, пусто строки, ложные значения и т. д.).Я также использую 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 (как я и ожидал)) однако lastName значение равно null (что не соответствует ожиданиям)

Я бы ожидал получить следующее:

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

Что я могу сделать для частичного обновления вложенного документа с помощью mgo?

1 Ответ

0 голосов
/ 28 мая 2018

Сначала давайте быстро объясним ваше поле 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, поле 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"`
}

Обратите внимание, что BasicInfo должен быть не указателем, если используется ,inline.Это не проблема, так как мы можем оставить его пустой структурой, если его поля не должны быть изменены, так как его поля являются указателями, поэтому оставляя их nil не изменит их.

Doing "manual"update

Если вам нужно использовать встроенный документ, библиотека mgo позволяет обновлять определенные поля встроенных документов, но затем вам нужно" вручную "создать документ обновления, как в этом примере:

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

Да, это совсем не удобно.Если вам это нужно много раз для разных типов, вы можете создать служебную функцию, которая использует отражение, рекурсивно перебирает поля и собирает документ обновления из полей, которые не являются nil.Затем вы можете передать этот динамически сгенерированный документ обновления, например, на UpdateId().

...