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