Frågan ovan returnerar dokument som "nästan" matchar User
dokument, men de har också inlägg från varje användare. Så i grund och botten är resultatet en serie User
dokument med en Post
array eller segment inbäddad .
Ett sätt skulle vara att lägga till en Posts []*Post
fältet till User
själv, och vi skulle vara klara:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Även om detta fungerar verkar det "överkill" att utöka User
med Posts
bara för en enda frågas skull. Om vi skulle fortsätta på den här vägen, vår User
typ skulle bli uppsvälld med massor av "extra" fält för olika frågor. För att inte tala om om vi fyller Posts
fältet och spara användaren, skulle dessa inlägg hamna sparade i User
dokumentera. Inte vad vi vill ha.
Ett annat sätt skulle vara att skapa en UserWithPosts
typ kopiering User
, och lägga till en Posts []*Post
fält. Onödigt att säga att detta är fult och oflexibelt (alla ändringar som gjorts i User
skulle behöva återspeglas i UserWithPosts
manuellt).
Med Struct Embedding
Istället för att ändra den ursprungliga User
, och istället för att skapa en ny UserWithPosts
skriv från "scratch", kan vi använda struct inbäddning
(återanvändning av befintlig User
och Post
typer) med ett litet trick:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Notera bson taggvärdet
",inline"
. Detta finns dokumenterat på bson.Marshal()
och bson.Unmarshal()
(vi kommer att använda det för att urskilja):
Genom att använda inbäddning och ",inline"
taggvärdet, UserWithPosts
typen i sig kommer att vara ett giltigt mål för att dela upp User
dokument och dess Post []*Post
fältet kommer att vara ett perfekt val för de uppslagna "posts"
.
Använder det:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
Eller få alla resultat i ett steg:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
Typdeklarationen för UserWithPosts
kan eller kanske inte är en lokal deklaration. Om du inte behöver det någon annanstans kan det vara en lokal deklaration i funktionen där du kör och bearbetar aggregeringsfrågan, så det kommer inte att blåsa upp dina befintliga typer och deklarationer. Om du vill återanvända den kan du deklarera den på paketnivå (exporterad eller ej exporterad) och använda den var du än behöver den.
Ändra aggregeringen
Ett annat alternativ är att använda MongoDB:s $replaceRoot
för att "ordna om" resultatdokumenten, så en "enkel" struktur täcker dokumenten 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 denna ommappning kan resultatdokumenten modelleras så här:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Observera att medan detta fungerar, gör posts
fältet för alla dokument kommer att hämtas från servern två gånger:en gång som posts
fältet för de returnerade dokumenten, och en gång som fältet för user
; vi kartlägger / använder det inte men det finns i resultatdokumenten. Så om den här lösningen väljs visas user.posts
fält bör tas bort t.ex. med ett $project
steg:
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}},
})