La requête ci-dessus renvoie les documents qui correspondent "presque" à User
documents, mais ils ont aussi les messages de chaque utilisateur. Donc, fondamentalement, le résultat est une série de User
documents avec un Post
tableau ou tranche intégré .
Une façon serait d'ajouter un Posts []*Post
champ à l'User
lui-même, et nous aurions fini :
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Bien que cela fonctionne, il semble "exagéré" d'étendre User
avec Posts
juste pour le plaisir d'une seule requête. Si nous continuons dans cette voie, notre User
le type serait gonflé avec beaucoup de champs "supplémentaires" pour différentes requêtes. Sans oublier si on remplit les Posts
champ et enregistrez l'utilisateur, ces messages finiraient par être enregistrés dans le User
document. Pas ce que nous voulons.
Une autre façon serait de créer un UserWithPosts
tapez en copiant User
, et en ajoutant un Posts []*Post
champ. Inutile de dire que c'est moche et inflexible (toute modification apportée à User
devrait être reflété dans UserWithPosts
manuellement).
Avec l'intégration de structures
Au lieu de modifier le User
d'origine , et au lieu de créer un nouveau UserWithPosts
tapez à partir de "zéro", nous pouvons utiliser struct embedding
(en réutilisant le User
existant et Post
types) avec une petite astuce :
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Notez la valeur de la balise bson
",inline"
. Ceci est documenté sur bson.Marshal()
et bson.Unmarshal()
(nous l'utiliserons pour unmarshaling):
En utilisant l'incorporation et le ",inline"
valeur de balise, le UserWithPosts
le type lui-même sera une cible valide pour le désassemblage de User
documents, et son Post []*Post
sera un choix parfait pour les "posts"
recherchés .
Utilisation :
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
Ou obtenir tous les résultats en une seule étape :
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
La déclaration de type de UserWithPosts
peut ou non être une déclaration locale. Si vous n'en avez pas besoin ailleurs, il peut s'agir d'une déclaration locale dans la fonction où vous exécutez et traitez la requête d'agrégation, afin de ne pas gonfler vos types et déclarations existants. Si vous souhaitez le réutiliser, vous pouvez le déclarer au niveau du package (exporté ou non exporté) et l'utiliser partout où vous en avez besoin.
Modifier l'agrégation
Une autre option consiste à utiliser $replaceRoot
de MongoDB
pour "réorganiser" les documents de résultat, ainsi une structure "simple" couvrira parfaitement les 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",
},
},
},
})
Avec ce remappage, les documents de résultat peuvent être modélisés comme ceci :
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Notez que pendant que cela fonctionne, les posts
le champ de tous les documents sera extrait deux fois du serveur :une fois en tant que posts
champ des documents retournés, et une fois comme champ de user
; nous ne le mappons pas / ne l'utilisons pas mais il est présent dans les documents de résultat. Donc si cette solution est choisie, le user.posts
champ doit être supprimé, par ex. avec un $project
étape :
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}},
})