Powyższe zapytanie zwraca dokumenty, które „prawie” pasują do User dokumenty, ale mają też posty każdego użytkownika. Tak więc zasadniczo wynikiem jest seria User dokumenty z Post tablica lub plaster osadzone .
Jednym ze sposobów byłoby dodanie Posts []*Post pole do User i skończymy:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Chociaż to działa, wydaje się, że rozszerzenie User wydaje się „przesadne” z Posts tylko ze względu na jedno zapytanie. Jeśli będziemy kontynuować tę drogę, nasz User typ zostałby nadęty z dużą ilością „dodatkowych” pól dla różnych zapytań. Nie wspominając o tym, czy wypełnimy Posts i zapisz użytkownika, te posty zostaną zapisane w User dokument. Nie to, czego chcemy.
Innym sposobem byłoby utworzenie UserWithPosts wpisz kopiowanie User i dodanie Posts []*Post pole. Nie trzeba dodawać, że jest to brzydkie i nieelastyczne (wszelkie zmiany wprowadzone w User musiałby zostać odzwierciedlony w UserWithPosts ręcznie).
Z osadzaniem strukturalnym
Zamiast modyfikować oryginalnego User i zamiast tworzyć nowy UserWithPosts wpisz od „scratch”, możemy wykorzystać osadzanie struktur
(ponowne wykorzystanie istniejącego User i Post typy) z małą sztuczką:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Zwróć uwagę na wartość tagu bson
",inline" . Jest to udokumentowane w bson.Marshal()
i bson.Unmarshal()
(wykorzystamy go do unmarshalingu):
Używając osadzania i ",inline" wartość tagu, UserWithPosts sam typ będzie prawidłowym celem dla przekazania User dokumenty i ich Post []*Post pole będzie idealnym wyborem dla szukanych "posts" .
Korzystanie z niego:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
Lub uzyskanie wszystkich wyników w jednym kroku:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
Deklaracja typu UserWithPosts może, ale nie musi być deklaracją lokalną. Jeśli nie potrzebujesz go gdzie indziej, może to być lokalna deklaracja w funkcji, w której wykonujesz i przetwarzasz zapytanie agregujące, więc nie rozdęje istniejących typów i deklaracji. Jeśli chcesz go ponownie użyć, możesz zadeklarować go na poziomie pakietu (eksportowanym lub nieeksportowanym) i używać go wszędzie, gdzie jest potrzebny.
Modyfikowanie agregacji
Inną opcją jest użycie $replaceRoot MongoDB
aby "przeorganizować" dokumenty wynikowe, tak aby "prosta" struktura idealnie pokryła dokumenty:
// 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",
},
},
},
})
Dzięki temu ponownemu mapowaniu dokumenty wynikowe mogą być modelowane w następujący sposób:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Zauważ, że chociaż to działa, posts pole wszystkich dokumentów zostanie pobrane z serwera dwukrotnie:raz jako posts pole zwracanych dokumentów, a raz jako pole user; nie mapujemy / nie używamy go, ale jest on obecny w dokumentach wynikowych. Jeśli więc zostanie wybrane to rozwiązanie, user.posts pole należy usunąć np. z $project etap:
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}},
})