De bovenstaande query retourneert documenten die "bijna" overeenkomen met User
documenten, maar ze hebben ook de berichten van elke gebruiker. Dus eigenlijk is het resultaat een reeks User
documenten met een Post
matrix of segment ingesloten .
Een manier zou zijn om een Posts []*Post
. toe te voegen veld naar de User
zelf, en we zouden klaar zijn:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Hoewel dit werkt, lijkt het "overkill" om User
uit te breiden met Posts
gewoon omwille van een enkele vraag. Als we op deze weg zouden doorgaan, zou onze User
type zou opgeblazen worden met veel "extra" velden voor verschillende zoekopdrachten. Om nog maar te zwijgen van het feit of we de Posts
vullen veld en sla de gebruiker op, die berichten zouden uiteindelijk worden opgeslagen in de User
document. Niet wat we willen.
Een andere manier zou zijn om een UserWithPosts
. te maken type kopiëren User
, en het toevoegen van een Posts []*Post
veld. Onnodig te zeggen dat dit lelijk en inflexibel is (alle wijzigingen aangebracht in User
zou moeten worden weerspiegeld in UserWithPosts
handmatig).
Met structurele inbedding
In plaats van de originele User
te wijzigen , en in plaats van een nieuwe UserWithPosts
. te maken type from "scratch", we kunnen struct embedding
gebruiken (hergebruik van de bestaande User
en Post
typen) met een klein trucje:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Let op de bson tagwaarde
",inline"
. Dit is gedocumenteerd op bson.Marshal()
en bson.Unmarshal()
(we zullen het gebruiken voor het ontrafelen):
Door insluiten te gebruiken en de ",inline"
tagwaarde, de UserWithPosts
type zelf zal een geldig doelwit zijn voor het ontrappen van User
documenten, en zijn Post []*Post
veld zal een perfecte keuze zijn voor de opgezochte "posts"
.
Het gebruiken:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
Of alle resultaten in één stap krijgen:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
De typedeclaratie van UserWithPosts
kan al dan niet een lokale aangifte zijn. Als je het nergens anders nodig hebt, kan het een lokale declaratie zijn in de functie waar je de aggregatiequery uitvoert en verwerkt, zodat het je bestaande typen en declaraties niet opzwelt. Als u het opnieuw wilt gebruiken, kunt u het op pakketniveau declareren (geëxporteerd of niet-geëxporteerd) en gebruiken waar u het nodig heeft.
De aggregatie wijzigen
Een andere optie is om MongoDB's $replaceRoot
te gebruiken
om de resultaatdocumenten te "herschikken", zodat een "eenvoudige" structuur de documenten perfect dekt:
// 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",
},
},
},
})
Met deze remapping kunnen de resultaatdocumenten als volgt worden gemodelleerd:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Merk op dat hoewel dit werkt, de posts
veld van alle documenten wordt twee keer van de server opgehaald:eenmaal als de posts
veld van de geretourneerde documenten, en eenmaal als het veld van user
; we brengen het niet in kaart / gebruiken het niet, maar het is aanwezig in de resultaatdocumenten. Dus als deze oplossing wordt gekozen, worden de user.posts
veld moet worden verwijderd, b.v. met een $project
stadium:
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}},
})