Mgo聚合:如何重用模型类型以查询和解组“混合”消息。结果? [英] Mgo aggregation: how to reuse model types to query and unmarshal "mixed" results?

查看:97
本文介绍了Mgo聚合:如何重用模型类型以查询和解组“混合”消息。结果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我们有2个馆藏:用户 帖子 ,按以下类型建模:

  type用户结构{
ID字符串`bson: _ id`
名称字符串`bson: name`
注册时间。时间`bson: registered`
}

type post struct {
ID字符串`bson: _ id`
用户ID字符串`bson: userID`
内容字符串`bson: content`
日期时间。时间`bson: date`
}

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

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

//插入新用户:
u:=&用户{
ID: 1,
名称: Bob,
已注册:time.Now(),
},
err:= usersColl.Insert(u)
//处理err

//获取帖子最近10分钟:
var个帖子s [] * Post
err:= postsColl.Find(
bson.M { date:bson.M { $ gt:time.Now()。Add(-10 * time。分钟)}},
).Limit(20).All(& posts)
//处理错误

如果我们使用聚合来获取以下内容的混合物,该怎么办?这些文件?例如 Collection.Pipe()

  //用帖子查询用户:
pipe:= collUsers.Pipe( [] bson.M {
{
$ lookup:bson.M {
from: posts,
localField: _id,
foreignField: userID,
as: posts,
},
},
})

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

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

解决方案

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



一种方法是将 Posts [] * Post 字段添加到用户本身,我们将完成:

  type用户结构{
ID字符串`bson: _ id`
名称字符串`bson: name`
注册时间。时间`bson: registered`
Posts [] * Post`bson: posts, omitempty`
}

虽然可行,但扩展 User 个带有 Posts 的帖子,仅用于单个查询。如果我们继续走这条路,我们的 User 类型将因大量用于不同查询的额外字段而变得肿。更不用说如果我们填写帖子字段并保存用户,这些帖子最终将保存在 User 文档中。不是我们想要的。



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



具有结构嵌入



而不是修改原始的 User ,而不是从 scratch创建新的 UserWithPosts 类型,我们可以利用结构嵌入(重用现有的 User Post 类型)一个小技巧:

  type UserWithPosts struct {
User`bson:,inline`
帖子[] *发布`bson: posts`
}

注意bson 标签值 ,内嵌 。记录在 bson.Marshal() bson.Unmarshal () (我们将使用它进行编组):


  inline内联字段,该字段必须是结构或映射。 
内联结构的处理方式就像其字段是外部结构的
的一部分一样。内联映射会导致将
与任何其他struct字段都不匹配的键插入
映射中,而不是照常丢弃。


通过嵌入和,内嵌 标记值, UserWithPosts 类型本身将成为解编 User 的有效目标文档,其 Post [] * Post 字段将是查找帖子 的理想选择。 / p>

使用它:

  var uwp * UserWithPosts 
it := pipe.Iter()
为此。Next(& uwp){
//使用uwp:
fmt.Println(uwp)
}
/ /处理它.Err()

或一步一步获取所有结果:

  var uwps [] * UserWithPosts 
err:= pipe.All(& uwps)
//处理错误

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



修改聚合



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

  //用帖子查询用户:
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`
帖子[] * Post`bson: posts`
}

请注意,尽管此方法有效,所有文档的code> posts 字段将从服务器获取两次:一次作为返回文档的 posts 字段,一次作为 user 的字段;我们没有映射/使用它,但是它存在于结果文档中。因此,如果选择此解决方案,则应删除 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}},
})


Let's say we have 2 collections: "users" and "posts", modeled by the following types:

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"`
}

These can be used when storing / retrieving individual or even collection of documents, e.g.:

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

What if we use aggregation to fetch a mixture of these documents? For example 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()

We query users with their posts in a single query. The result is a mixture of users and posts. How can we reuse our User and Post model types to not have to deal with the result as "raw" documents (of type bson.M)?

解决方案

The query above returns documents that "almost" match User documents, but they also have the posts of each users. So basically the result is a series of User documents with a Post array or slice embedded.

One way would be to add a Posts []*Post field to the User itself, and we would be done:

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

While this works, it seems "overkill" to extend User with Posts just for the sake of a single query. If we'd continue down this road, our User type would get bloated with lots of "extra" fields for different queries. Not to mention if we fill the Posts field and save the user, those posts would end up saved inside the User document. Not what we want.

Another way would be to create a UserWithPosts type copying User, and adding a Posts []*Post field. Needless to say this is ugly and inflexible (any changes made to User would have to be reflected to UserWithPosts manually).

With Struct Embedding

Instead of modifying the original User, and instead of creating a new UserWithPosts type from "scratch", we can utilize struct embedding (reusing the existing User and Post types) with a little trick:

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

Note the bson tag value ",inline". This is documented at bson.Marshal() and bson.Unmarshal() (we'll use it for unmarshaling):

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.

By using embedding and the ",inline" tag value, the UserWithPosts type itself will be a valid target for unmarshaling User documents, and its Post []*Post field will be a perfect choice for the looked up "posts".

Using it:

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

Or getting all results in one step:

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

The type declaration of UserWithPosts may or may not be a local declaration. If you don't need it elsewhere, it can be a local declaration in the function where you execute and process the aggregation query, so it will not bloat your existing types and declarations. If you want to reuse it, you can declare it at package level (exported or unexported), and use it wherever you need it.

Modifying the aggregation

Another option is to use MongoDB's $replaceRoot to "rearrange" the result documents, so a "simple" struct will perfectly cover the documents:

// 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",
            },
        },
    },
})

With this remapping, the result documents can be modeled like this:

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

Note that while this works, the posts field of all documents will be fetched from the server twice: once as the posts field of the returned documents, and once as the field of user; we don't map / use it but it is present in the result documents. So if this solution is chosen, the user.posts field should be removed e.g. with a $project stage:

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}},
})

这篇关于Mgo聚合:如何重用模型类型以查询和解组“混合”消息。结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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