Die obige Abfrage gibt Dokumente zurück, die "fast" mit User
übereinstimmen Dokumente, aber sie haben auch die Beiträge der einzelnen Benutzer. Das Ergebnis ist also im Grunde eine Reihe von User
Dokumente mit einem Post
Array oder Slice eingebettet .
Eine Möglichkeit wäre, einen Posts []*Post
hinzuzufügen Feld an den User
selbst, und wir wären fertig:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Während dies funktioniert, scheint es "übertrieben" zu sein, User
zu erweitern mit Posts
nur wegen einer einzigen Abfrage. Wenn wir diesen Weg fortsetzen würden, unser User
type würde mit vielen "zusätzlichen" Feldern für verschiedene Abfragen aufgebläht. Ganz zu schweigen davon, ob wir die Posts
füllen eingeben und den Benutzer speichern, würden diese Beiträge im User
gespeichert dokumentieren. Nicht das, was wir wollen.
Eine andere Möglichkeit wäre, einen UserWithPosts
zu erstellen geben Sie Kopieren von User
ein und Hinzufügen eines Posts []*Post
aufstellen. Unnötig zu erwähnen, dass dies hässlich und unflexibel ist (jede Änderung, die an User
müsste in UserWithPosts
widergespiegelt werden manuell).
Mit Struktureinbettung
Anstatt den ursprünglichen User
zu ändern , und anstatt einen neuen UserWithPosts
zu erstellen Typ von "scratch", wir können struct embedding
verwenden (Wiederverwendung des vorhandenen User
und Post
Typen) mit einem kleinen Trick:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Beachten Sie den Tag-Wert bson
",inline"
. Dies ist dokumentiert unter bson.Marshal()
und bson.Unmarshal()
(Wir verwenden es zum Unmarshaling):
Durch die Verwendung von Embedding und dem ",inline"
Tag-Wert, der UserWithPosts
type selbst wird ein gültiges Ziel für das Unmarshaling von User
sein Dokumente und deren Post []*Post
Feld ist eine perfekte Wahl für die nachgeschlagenen "posts"
.
Verwendung:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
Oder Sie erhalten alle Ergebnisse in einem Schritt:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
Die Typdeklaration von UserWithPosts
kann eine lokale Deklaration sein oder nicht. Wenn Sie es an keiner anderen Stelle benötigen, kann es sich um eine lokale Deklaration in der Funktion handeln, in der Sie die Aggregationsabfrage ausführen und verarbeiten, damit Ihre vorhandenen Typen und Deklarationen nicht aufgebläht werden. Wenn Sie es wiederverwenden möchten, können Sie es auf Paketebene deklarieren (exportiert oder nicht exportiert) und verwenden, wo immer Sie es brauchen.
Ändern der Aggregation
Eine weitere Option ist die Verwendung von $replaceRoot
von MongoDB
um die Ergebnisdokumente "umzuordnen", sodass eine "einfache" Struktur die Dokumente perfekt abdeckt:
// 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",
},
},
},
})
Mit dieser Neuzuordnung können die Ergebnisdokumente wie folgt modelliert werden:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Beachten Sie, dass, während dies funktioniert, die posts
Feld aller Dokumente wird zweimal vom Server geholt:einmal als posts
Feld der zurückgegebenen Dokumente und einmal als Feld von user
; wir mappen/verwenden es nicht, aber es ist in den Ergebnisdokumenten vorhanden. Wird also diese Lösung gewählt, wird die user.posts
Feld sollte entfernt werden, z. mit einem $project
Stufe:
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}},
})