Truy vấn ở trên trả về các tài liệu "gần như" khớp với User
tài liệu, nhưng chúng cũng có bài đăng của từng người dùng. Vì vậy, về cơ bản kết quả là một loạt User
tài liệu có Post
mảng hoặc lát nhúng .
Một cách là thêm Posts []*Post
vào trường User
chính nó, và chúng tôi sẽ hoàn thành:
type User struct {
ID string `bson:"_id"`
Name string `bson:"name"`
Registered time.Time `bson:"registered"`
Posts []*Post `bson:"posts,omitempty"`
}
Trong khi điều này hoạt động, có vẻ như "quá mức cần thiết" để mở rộng User
với Posts
chỉ vì lợi ích của một truy vấn duy nhất. Nếu chúng tôi tiếp tục con đường này, User
của chúng tôi loại sẽ trở nên cồng kềnh với rất nhiều trường "bổ sung" cho các truy vấn khác nhau. Chưa kể nếu chúng tôi điền vào Posts
và lưu người dùng, những bài đăng đó cuối cùng sẽ được lưu bên trong User
tài liệu. Không phải những gì chúng tôi muốn.
Một cách khác là tạo UserWithPosts
gõ sao chép User
và thêm một Posts []*Post
đồng ruộng. Không cần phải nói điều này xấu và không linh hoạt (bất kỳ thay đổi nào được thực hiện đối với User
sẽ phải được phản ánh tới UserWithPosts
thủ công).
Với tính năng Nhúng cấu trúc
Thay vì sửa đổi User
ban đầu và thay vì tạo UserWithPosts
mới nhập từ "đầu", chúng tôi có thể sử dụng nhúng cấu trúc
(sử dụng lại User
hiện có và Post
loại) với một thủ thuật nhỏ:
type UserWithPosts struct {
User `bson:",inline"`
Posts []*Post `bson:"posts"`
}
Lưu ý giá trị thẻ bson
",inline"
. Điều này được ghi lại tại bson.Marshal()
và bson.Unmarshal()
(chúng tôi sẽ sử dụng nó để giải nén):
Bằng cách sử dụng tính năng nhúng và ",inline"
giá trị thẻ, UserWithPosts
loại chính nó sẽ là một mục tiêu hợp lệ để giải nén User
tài liệu và Post []*Post
của nó trường sẽ là một lựa chọn hoàn hảo cho "posts"
đã tra cứu .
Sử dụng nó:
var uwp *UserWithPosts
it := pipe.Iter()
for it.Next(&uwp) {
// Use uwp:
fmt.Println(uwp)
}
// Handle it.Err()
Hoặc nhận tất cả kết quả trong một bước:
var uwps []*UserWithPosts
err := pipe.All(&uwps)
// Handle error
Khai báo kiểu của UserWithPosts
có thể là một khai báo cục bộ hoặc không. Nếu bạn không cần nó ở nơi khác, nó có thể là một khai báo cục bộ trong hàm nơi bạn thực thi và xử lý truy vấn tổng hợp, vì vậy nó sẽ không làm cồng kềnh các kiểu và khai báo hiện có của bạn. Nếu bạn muốn sử dụng lại nó, bạn có thể khai báo nó ở cấp độ gói (đã xuất hoặc chưa báo cáo) và sử dụng nó ở bất cứ đâu bạn cần.
Sửa đổi tổng hợp
Một tùy chọn khác là sử dụng $replaceRoot
của MongoDB
để "sắp xếp lại" các tài liệu kết quả, vì vậy cấu trúc "đơn giản" sẽ bao phủ hoàn hảo các tài liệu:
// 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",
},
},
},
})
Với ánh xạ lại này, các tài liệu kết quả có thể được mô hình hóa như thế này:
type UserWithPosts struct {
User *User `bson:"user"`
Posts []*Post `bson:"posts"`
}
Lưu ý rằng trong khi điều này hoạt động, posts
trường của tất cả các tài liệu sẽ được tìm nạp từ máy chủ hai lần:một lần khi posts
trường của các tài liệu được trả lại và từng là trường của user
; chúng tôi không ánh xạ / sử dụng nó nhưng nó có trong các tài liệu kết quả. Vì vậy, nếu giải pháp này được chọn, user.posts
trường nên được loại bỏ, ví dụ:với $project
giai đoạn:
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}},
})