自定义mgo upsert操作 [英] Customize mgo upsert operation

查看:356
本文介绍了自定义mgo upsert操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个游戏分析休息API,用于存储玩家的平均表现统计数据。当新的统计数据到达时,我想通过将新增量合并到现有文档中来更新Mongodb中的现有游戏记录。我还存储过去的分析数据。因此,我可以返回数据,例如自游戏上次更新以来玩家的统计数据正在减少或增加。



问题是:当我想要使用mgo将我的新游戏数据加入Mongodb,它会覆盖玩家的所有统计数组。其实,这是预料之中的。我知道如何修复它,如果我可以修改我的文档,mgo试图插入到Mongodb中。


$ b

问题:如何自定义mgo upsert行为?所以我可以在 Player.Stats 前加一个 $ push 运算符来防止Mongodb擦除 stats 数组在文档中。



我真正的问题:即将使用。我会以某种方式解决它。我真正想知道的是:如何在upsert之前自定义mgo的行为?



某些解决方案:我尝试了一些解决方案我以前。像编码/解码游戏结构到 bson.M 来自定义它。但是,我发现它很繁琐和混乱。如果没有其他方法,我会使用它。



块:我不想手写所有结构字段与 bson.M ,只需在一个字段上使用 $ push 运算符。因为有几十个字段,这会很容易出错,并且会增加我的代码复杂度。




示例:

  //假设这是Mongodb中的现有游戏:
existingGame:= Game {
ID:1,
名称:现有游戏,
//游戏只有一个玩家
玩家:[]玩家{
//玩家有一些统计数据。最新的是2.0。
{1,foo,[] {3.5,2.0}},
}
}

//这是一个新的请求到我的API
//我想把它插入现有的游戏
newGame:=游戏{
ID:1,
玩家:[]玩家{
//正如预期的那样,这将重置玩家foo的统计数据为5.0
//
//插入后,我希望它是:
//
// [] {3.5,2.0,5.0 }
//
//在Mongodb中
{1,foo,[] {5.0}},
}
}

//例子2:
//如果新的游戏请求是这样的:
newGame:= Game {ID:1,Players:[] Player {{1,foo,[] {5.0} ,{1,bar,[] {6.7}}}}
//我期待这个结果:
Game {ID:1,Players:[] Player {{1,foo ,[] {3.5,2.0,5.0},{1,bar,[] {6.7}}}}

func(db * Store)合并(newGame * Game)error {
sess:= db.session.Copy()
推迟sess.Close()

col:= sess.DB(foo)。C(games)
//我想修改newGame他重新添加一个$ push运算符
//到一个新的`bson.M`或`bson.D`中以使mgo能够插入
//我的新增量而不需要重新设置玩家的统计信息
_,err:= col.UpsertId(newGame.ID,newGame)

return err
}

类型游戏结构{
ID int`bson :_ id`
名称字符串
玩家[]玩家`bson:,omitempty`
// ...为简单起见,我忽略了其他细节...
}

类型玩家结构{
//将玩家连接到游戏
GameID int`bson:game_id`
名称字符串
/ /我想保留以前的统计数据
//所以,这就是为什么我在这里使用数组
Stats [] float64
// ...
}

我在控制台尝试了这个Mongodb命令来更新特定游戏的玩家:

  db.competitions.update({
_id:1,
players.game_id:1
},{
$ push:{
players。$。st ats:3
}
},{
upsert:true
})


解决方案

要回答我的真实问题:如何在upsert之前自定义mgo的行为? - 您可以自定义bson通过为模型定义 bson Getter 进行编组。为了说明它是如何工作的,我们简化模型以避免嵌套文档:

  b 














$ b $ >


使用newGame如下:

  newGame: =游戏{
ID:1,
名称:foo,
Stats:[] {5.0}
}

更新 col.UpsertId(newGame.ID,newGame)默认marshals

  update({_ id:1},{name> newGame )转换为JSON, :foo,stats:[5]},{upsert:true}); 

要使用 $ set $ push 等,您可以定义一个自定义bson getter。例如

  func(g Game)GetBSON()(interface {},error){
return bson.M {
$ set:bson.M {name:g.Name},
$ push:bson.M {stats:bson.M {$ each:g。









$ p $因此,更新 col.UpsertId(newGame.ID,newGame)将产生一个mongodb查询

 更新({_ id:1},{$ set:{name:foo},$ push:{stats:{$ each:[5]}}},{upsert:true}); 

为了让它看起来很清晰 - 自定义封送拆收将用于所有的mgo查询,所以您可能不会不想直接将其定义为模型,而是要将其派生为仅用于upsert操作:

  type更新游戏结构{ 
Game


func(g UpdatedGame)GetBSON()(interface {},error){
return bson.M {....}




newGame:=游戏{
ID:1,
名称:foo,
Stats:[] {5.0}
}

col.UpsertId(newGame.ID,UpdatedGame {newGame})


I've a game analytics rest API which stores the average performance statistics of the players. When a new statistic arrives, I want to update the existing game record in Mongodb by merging the new delta onto the existing document. I'm storing the past analytics data as well. So that, I can return data like the player's stats are decreasing or increasing since the game's last update.

The problem is: When I want to upsert my new game data into Mongodb with mgo, it overwrites all of a player's stats array. Actually, this is expected. I know how to fix it if I can modify my document that mgo tries to upsert into Mongodb.

Question: How can I customize mgo upsert behaviour? So that I can add a $push operator in front of Player.Stats to prevent Mongodb erasing the stats array inside the document.

My Real Question: It doesn't matter which Mongo commands I'm going to use. I'll figure it out somehow. What I actually want to know is: How can I customize the behaviour of mgo before upsert?

Some Solutions: I've tried some solutions myself before. Like, encoding/decoding Game struct into bson.M to customize it. However, I found it cumbersome and messy. If there's no other way, I'd use it.

Blocks: I don't want to hand-write all of my structs fields with bson.M, just to use a $push operator on one field. Because there are dozens of fields, that would be error-prone and will increase my code complexity.


Example:

// Assume that, this is an existing game in Mongodb:
existingGame := Game{
    ID: 1,
    Name: "Existing game",
    // The game has just one player
    Players: []Player{
        // The player has some stats. The newest one is 2.0.
        {1, "foo", []{3.5, 2.0}},
    }
}

// This is a new request coming to my API
// I want to upsert this into the existing Game
newGame := Game{
    ID: 1,
    Players: []Player{
        // As expectedly, this will reset player foo's stats to 5.0
        //
        // After upserting, I want it to be as: 
        //
        // []{3.5, 2.0, 5.0}
        //
        // in Mongodb
        {1, "foo", []{5.0}},
    }
}

// Example 2:
// If new Game request like this:
newGame := Game{ID: 1, Players: []Player{{1, "foo", []{5.0},{1, "bar", []{6.7}}}}
// I'm expecting this result:
Game{ID: 1, Players: []Player{{1, "foo", []{3.5, 2.0, 5.0},{1, "bar", []{6.7}}}}

func (db *Store) Merge(newGame *Game) error {
    sess := db.session.Copy()
    defer sess.Close()

    col := sess.DB("foo").C("games")
    // I want to modify newGame here to add a $push operator
    // into a new `bson.M` or `bson.D` to make mgo to upsert
    // my new delta without resetting the player stats
    _, err := col.UpsertId(newGame.ID, newGame)

    return err
}

type Game struct {
    ID int `bson:"_id"`
    Name string
    Players []Player `bson:",omitempty"`
    // ...I omitted other details for simplicity here...
}

type Player struct {
    // This connects the player to the game
    GameID int `bson:"game_id"`
    Name string
    // I want to keep the previous values of stats
    // So, that's why I'm using an array here
    Stats []float64
    // ...
}

I tried this Mongodb command in the console to update the specific game's player:

db.competitions.update({
   _id: 1,
   "players.game_id": 1
}, {
   $push: { 
       "players.$.stats": 3
   }
}, {
   upsert: true
})

解决方案

To answer the "My Real Question: How can I customize the behaviour of mgo before upsert?" - you can customise bson marshalling by defining bson Getter to the model.

To illustrate how it works, lets simplify the model to avoid nested documents:

type Game struct {
    ID int `bson:"_id"`
    Name string
    Stats [] float64
}

With newGame as following:

newGame := Game{
    ID: 1,
    Name: "foo",
    Stats: []{5.0}
}

The update col.UpsertId(newGame.ID, newGame) by default marshals newGame into JSON, producing mongo query like:

update({_id:1}, {name: "foo", stats: [5]}, {upsert: true});

To make use of $set, $push etc, you can define a custom bson getter. E.g.

func (g Game) GetBSON() (interface{}, error) {
    return bson.M{
        "$set": bson.M{"name": g.Name}, 
        "$push": bson.M{"stats": bson.M{"$each": g.Stats}},
    }, nil
}

So the update col.UpsertId(newGame.ID, newGame) will produce a mongodb query

update({_id:1}, {$set: {name: "foo"}, $push: {stats: {$each: [5]}}}, {upsert: true});

To make it crystal clear - the custom marshaler will be used in all mgo queries, so you probably don't want to define it directly to the model, but to its derivative to use in upsert operations only:

type UpdatedGame struct {
    Game
}

func (g UpdatedGame) GetBSON() (interface{}, error) {
    return bson.M{....}
}

.....

newGame := Game{
    ID: 1,
    Name: "foo",
    Stats: []{5.0}
}

col.UpsertId(newGame.ID, UpdatedGame{newGame})

这篇关于自定义mgo upsert操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆