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}},
})