gpt4 book ai didi

mongodb - Mgo 聚集体 : how to reuse model types to query and unmarshal "mixed" results?

转载 作者:IT王子 更新时间:2023-10-29 02:21:34 26 4
gpt4 key购买 nike

假设我们有 2 个集合:“users”“posts”,由以下类型建模:

type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
}

type Post struct {
ID string `bson:"_id"`
UserID string `bson:"userID"`
Content string `bson:"content"`
Date time.Time `bson:"date"`
}

这些可以在存储/检索单个甚至文档集合时使用,例如:

usersColl := sess.DB("").C("users")
postsColl := sess.DB("").C("posts")

// Insert new user:
u := &User{
ID: "1",
Name: "Bob",
Registered: time.Now(),
},
err := usersColl.Insert(u)
// Handle err

// Get Posts in the last 10 mintes:
var posts []*Post
err := postsColl.Find(
bson.M{"date": bson.M{"$gt": time.Now().Add(-10 * time.Minute)}},
).Limit(20).All(&posts)
// Handle err

如果我们使用 aggregation 会怎样?获取这些文件的混合物?例如Collection.Pipe() :

// Query users with their posts:
pipe := collUsers.Pipe([]bson.M{
{
"$lookup": bson.M{
"from": "posts",
"localField": "_id",
"foreignField": "userID",
"as": "posts",
},
},
})

var doc bson.M
it := pipe.Iter()
for it.Next(&doc) {
fmt.Println(doc)
}
// Handle it.Err()

我们在单个查询中查询用户的帖子。结果是用户和帖子的混合体。我们如何重用我们的 UserPost 模型类型,而不必将结果作为“原始”文档(bson.M 类型)处理)?

最佳答案

上面的查询返回“几乎”匹配 User 文档的文档,但它们也有每个用户的帖子。所以基本上结果是一系列 User 文档,带有 Post 数组或 slice 嵌入

一种方法是将 Posts []*Post 字段添加到 User 本身,这样我们就完成了:

type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}

虽然这可行,但仅仅为了单个查询而使用 Posts 扩展 User 似乎“矫枉过正”。如果我们继续沿着这条路走下去,我们的 User 类型会变得臃肿,因为有很多用于不同查询的“额外”字段。更不用说如果我们填写 Posts 字段并保存用户,这些帖子最终会保存在 User 文档中。不是我们想要的。

另一种方法是创建一个 UserWithPosts 类型复制 User,并添加一个 Posts []*Post 字段。不用说这是丑陋和不灵活的(对 User 所做的任何更改都必须手动反射(reflect)到 UserWithPosts)。

使用结构嵌入

我们可以利用 struct embedding 而不是修改原始的 User,也不是从“scratch”创建新的 UserWithPosts 类型。 (重用现有的 UserPost 类型)有一个小技巧:

type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}

注意 bson tag value ",内联"。这记录在 bson.Marshal()bson.Unmarshal() (我们将用它来解码):

inline     Inline the field, which must be a struct or a map.
Inlined structs are handled as if its fields were part
of the outer struct. An inlined map causes keys that do
not match any other struct field to be inserted in the
map rather than being discarded as usual.

通过使用嵌入和 ",inline" 标签值,UserWithPosts 类型本身将成为解码 User 文档的有效目标,及其 Post []*Post 字段将是查找 “posts” 的完美选择。

使用它:

var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()

或者一步得到所有结果:

var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error

UserWithPosts 的类型声明可能是也可能不是局部声明。如果您在其他地方不需要它,它可以是您执行和处理聚合查询的函数中的局部声明,因此它不会使您现有的类型和声明膨胀。如果你想重用它,你可以在包级别声明它(导出或未导出),并在你需要的任何地方使用它。

修改聚合

另一种选择是使用 MongoDB 的 $replaceRoot “重新排列”结果文档,因此“简单”结构将完美覆盖文档:

// Query users with their posts:
pipe := collUsers.Pipe([]bson.M{
{
"$lookup": bson.M{
"from": "posts",
"localField": "_id",
"foreignField": "userID",
"as": "posts",
},
},
{
"$replaceRoot": bson.M{
"newRoot": bson.M{
"user": "$$ROOT",
"posts": "$posts",
},
},
},
})

通过这种重新映射,结果文档可以像这样建模:

type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}

请注意,虽然这有效,但所有文档的 posts 字段将从服务器获取两次:一次作为返回文档的 posts 字段,另一次作为用户字段;我们不映射/使用它,但它存在于结果文档中。因此,如果选择此解决方案,则应删除 user.posts 字段,例如使用 $project 阶段:

pipe := collUsers.Pipe([]bson.M{
{
"$lookup": bson.M{
"from": "posts",
"localField": "_id",
"foreignField": "userID",
"as": "posts",
},
},
{
"$replaceRoot": bson.M{
"newRoot": bson.M{
"user": "$$ROOT",
"posts": "$posts",
},
},
},
{"$project": bson.M{"user.posts": 0}},
})

关于mongodb - Mgo 聚集体 : how to reuse model types to query and unmarshal "mixed" results?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46774424/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com