Kueri di atas mengembalikan dokumen yang "hampir" cocok dengan User
dokumen, tetapi mereka juga memiliki posting masing-masing pengguna. Jadi pada dasarnya hasilnya adalah serangkaian User
dokumen dengan Post
larik atau irisan tertanam .
Salah satu caranya adalah dengan menambahkan Posts []*Post
bidang ke User
sendiri, dan kita akan selesai:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Saat ini berfungsi, tampaknya "berlebihan" untuk memperluas User
dengan Post
hanya demi satu permintaan. Jika kami melanjutkan jalan ini, User
our type akan membengkak dengan banyak bidang "ekstra" untuk kueri yang berbeda. Belum lagi jika kita mengisi Posts
dan simpan pengguna, posting tersebut akan disimpan di dalam User
dokumen. Bukan yang kita inginkan.
Cara lain adalah dengan membuat UserWithPosts
ketik menyalin User
, dan menambahkan Posts []*Post
bidang. Tak perlu dikatakan ini jelek dan tidak fleksibel (perubahan apa pun yang dilakukan pada User
harus dicerminkan ke UserWithPosts
secara manual).
Dengan Penyematan Struktur
Alih-alih memodifikasi User
asli , dan alih-alih membuat UserWithPosts
baru ketik dari "scratch", kita dapat menggunakan penyematan struct
(menggunakan kembali User
yang ada dan Post
jenis) dengan sedikit trik:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Perhatikan bson nilai tag
",inline"
. Ini didokumentasikan di bson.Marshal()
dan bson.Unmarshal()
(kami akan menggunakannya untuk unmarshaling):
Dengan menggunakan penyematan dan ",inline"
nilai tag, UserWithPosts
type itu sendiri akan menjadi target yang valid untuk membongkar User
dokumen, dan Post []*Post
its bidang akan menjadi pilihan yang sempurna untuk "posts"
yang dicari .
Menggunakannya:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
Atau mendapatkan semua hasil dalam satu langkah:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
Deklarasi jenis UserWithPosts
mungkin atau mungkin bukan deklarasi lokal. Jika Anda tidak membutuhkannya di tempat lain, itu bisa berupa deklarasi lokal dalam fungsi tempat Anda menjalankan dan memproses kueri agregasi, sehingga tidak akan menggembungkan jenis dan deklarasi yang ada. Jika Anda ingin menggunakannya kembali, Anda dapat mendeklarasikannya di tingkat paket (diekspor atau tidak diekspor), dan menggunakannya di mana pun Anda membutuhkannya.
Memodifikasi agregasi
Opsi lainnya adalah menggunakan $replaceRoot
MongoDB
untuk "mengatur ulang" dokumen hasil, sehingga struktur "sederhana" akan menutupi dokumen dengan sempurna:
// 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",
},
},
},
})
Dengan pemetaan ulang ini, dokumen hasil dapat dimodelkan seperti ini:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Perhatikan bahwa saat ini berfungsi, posts
bidang semua dokumen akan diambil dari server dua kali:sekali sebagai posts
bidang dokumen yang dikembalikan, dan sekali sebagai bidang user
; kami tidak memetakan / menggunakannya tetapi ada di dokumen hasil. Jadi jika solusi ini dipilih, user.posts
bidang harus dihapus mis. dengan $project
panggung:
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}},
})