La query precedente restituisce documenti che "quasi" corrispondono a User
documenti, ma hanno anche i post di ogni utente. Quindi sostanzialmente il risultato è una serie di User
documenti con un Posts
array o slice embedded .
Un modo sarebbe aggiungere un Posts []*Post
campo all'User
stesso, e avremmo finito:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Anche se funziona, sembra "eccessivo" estendere User
con Posts
solo per il bene di una singola query. Se dovessimo continuare su questa strada, il nostro User
il tipo si riempirebbe di molti campi "extra" per query diverse. Per non parlare se riempiamo i Posts
campo e salva l'utente, quei post finirebbero per essere salvati all'interno di User
documento. Non quello che vogliamo.
Un altro modo sarebbe creare un UserWithPosts
digita copiando User
e aggiungendo un Posts []*Post
campo. Inutile dire che questo è brutto e inflessibile (qualsiasi modifica apportata a User
dovrebbe riflettersi in UserWithPosts
manualmente).
Con struct Embedding
Invece di modificare l'User
originale e invece di creare un nuovo UserWithPosts
digitare da "scratch", possiamo utilizzare incorporamento di strutture
(riutilizzando l'User
esistente e Post
tipi) con un piccolo trucco:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Nota il valore del tag bson
",inline"
. Ciò è documentato in bson.Marshal()
e bson.Unmarshal()
(lo useremo per l'annullamento del marshalling):
Usando l'incorporamento e il ",inline"
valore del tag, il UserWithPosts
il tipo stesso sarà un obiettivo valido per l'annullamento del marshalling di User
documenti e il relativo Posts []*Post
campo sarà una scelta perfetta per i "posts"
cercati .
Usandolo:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
O ottenere tutti i risultati in un solo passaggio:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
La dichiarazione del tipo di UserWithPosts
può essere o meno una dichiarazione locale. Se non ne hai bisogno altrove, può essere una dichiarazione locale nella funzione in cui esegui ed elabori la query di aggregazione, quindi non gonfierà i tuoi tipi e dichiarazioni esistenti. Se vuoi riutilizzarlo, puoi dichiararlo a livello di pacchetto (esportato o non esportato) e usarlo dove ti serve.
Modifica dell'aggregazione
Un'altra opzione è utilizzare $replaceRoot
di MongoDB.
per "riordinare" i documenti risultanti, in modo che una struttura "semplice" copra perfettamente i documenti:
// 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",
},
},
},
})
Con questa rimappatura, i documenti dei risultati possono essere modellati in questo modo:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Nota che mentre funziona, i posts
campo di tutti i documenti verrà prelevato dal server due volte:una volta come posts
campo dei documenti restituiti e una volta come campo di user
; non lo mappiamo / lo usiamo ma è presente nei documenti dei risultati. Quindi, se viene scelta questa soluzione, user.posts
il campo deve essere rimosso ad es. con un $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}},
})