Forespørgslen ovenfor returnerer dokumenter, der "næsten" matcher User
dokumenter, men de har også hver brugers indlæg. Så grundlæggende er resultatet en serie af User
dokumenter med en Post
matrix eller udsnit indlejret .
En måde ville være at tilføje en Posts []*Post
feltet til User
selv, og vi ville være færdige:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Selvom dette virker, virker det "overkill" at udvide User
med Posts
bare for en enkelt forespørgsel. Hvis vi fortsætter ad denne vej, vil vores User
type ville blive oppustet med masser af "ekstra" felter til forskellige forespørgsler. For ikke at nævne, om vi udfylder Posts
felt og gemme brugeren, ville disse indlæg ende med at blive gemt i User
dokument. Ikke hvad vi ønsker.
En anden måde ville være at oprette en UserWithPosts
type kopiering User
, og tilføjelse af en Posts []*Post
Mark. Det er overflødigt at sige, at dette er grimt og ufleksibelt (enhver ændring af User
skulle afspejles i UserWithPosts
manuelt).
Med Struct Embedding
I stedet for at ændre den originale User
, og i stedet for at oprette en ny UserWithPosts
skriv fra "scratch", kan vi bruge struct-indlejring
(genbruger den eksisterende User
og Post
typer) med et lille trick:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Bemærk bson tagværdien
",inline"
. Dette er dokumenteret på bson.Marshal()
og bson.Unmarshal()
(vi bruger det til unmarshaling):
Ved at bruge indlejring og ",inline"
tag-værdi, UserWithPosts
selve typen vil være et gyldigt mål for at opdele User
dokumenter, og dets Post []*Post
feltet vil være et perfekt valg til de slåede "posts"
.
Bruger det:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
Eller få alle resultater i ét trin:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
Typeerklæringen for UserWithPosts
kan være en lokal erklæring eller ikke. Hvis du ikke har brug for det andre steder, kan det være en lokal deklaration i funktionen, hvor du udfører og behandler aggregeringsforespørgslen, så det vil ikke blæse dine eksisterende typer og deklarationer op. Hvis du vil genbruge det, kan du erklære det på pakkeniveau (eksporteret eller ikke-eksporteret) og bruge det, hvor du har brug for det.
Ændring af sammenlægningen
En anden mulighed er at bruge MongoDB's $replaceRoot
at "omarrangere" resultatdokumenterne, så en "simpel" struktur dækker dokumenterne perfekt:
// 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",
},
},
},
})
Med denne remapping kan resultatdokumenterne modelleres således:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Bemærk, at mens dette virker, er posts
felt af alle dokumenter vil blive hentet fra serveren to gange:én gang som posts
feltet for de returnerede dokumenter, og én gang som feltet for user
; vi kortlægger/bruger det ikke, men det er til stede i resultatdokumenterne. Så hvis denne løsning vælges, vil user.posts
felt skal fjernes f.eks. med et $project
fase:
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}},
})