La consulta anterior devuelve documentos que "casi" coinciden con User
documentos, pero también tienen las publicaciones de cada usuario. Básicamente, el resultado es una serie de User
documentos con una Post
matriz o segmento incrustado .
Una forma sería agregar un Posts []*Post
campo al User
en sí mismo, y habríamos terminado:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Si bien esto funciona, parece "exagerado" extender User
con Posts
solo por el bien de una sola consulta. Si continuáramos por este camino, nuestro User
type se hincharía con muchos campos "adicionales" para diferentes consultas. Ni hablar si llenamos las Posts
y guarde al usuario, esas publicaciones terminarían guardadas dentro del User
documento. No es lo que queremos.
Otra forma sería crear un UserWithPosts
escriba copiando User
y agregar Posts []*Post
campo. No hace falta decir que esto es feo e inflexible (cualquier cambio realizado en User
tendría que reflejarse en UserWithPosts
manualmente).
Con incrustación de estructura
En lugar de modificar el User
original , y en lugar de crear un nuevo UserWithPosts
escriba desde "cero", podemos utilizar incrustación de estructuras
(reutilizando el User
existente y Post
tipos) con un pequeño truco:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Tenga en cuenta el valor de la etiqueta bson
",inline"
. Esto está documentado en bson.Marshal()
y bson.Unmarshal()
(lo usaremos para desarmar):
Al usar la incrustación y el ",inline"
valor de la etiqueta, el UserWithPosts
type en sí mismo será un objetivo válido para desarmar User
documentos y su Post []*Post
será una elección perfecta para las "posts"
buscadas .
Usándolo:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
O obtener todos los resultados en un solo paso:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
La declaración de tipo de UserWithPosts
puede ser o no una declaración local. Si no lo necesita en otro lugar, puede ser una declaración local en la función donde ejecuta y procesa la consulta de agregación, por lo que no inflará sus tipos y declaraciones existentes. Si desea reutilizarlo, puede declararlo a nivel de paquete (exportado o no exportado) y usarlo donde lo necesite.
Modificar la agregación
Otra opción es usar $replaceRoot
de MongoDB
para "reorganizar" los documentos de resultados, por lo que una estructura "simple" cubrirá perfectamente los documentos:
// 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",
},
},
},
})
Con esta reasignación, los documentos de resultados se pueden modelar así:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Tenga en cuenta que si bien esto funciona, las posts
El campo de todos los documentos se obtendrá del servidor dos veces:una vez como posts
campo de los documentos devueltos, y una vez como el campo de user
; no lo mapeamos ni lo usamos, pero está presente en los documentos de resultados. Entonces, si se elige esta solución, el user.posts
el campo debe eliminarse, p. con un $project
etapa:
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}},
})