Populate trong Mongoose

Phùng Hùng

Phùng Hùng

Teacher and Programmer

Mongoose có populate() là cách thức mạnh mẽ hơn thay thế cho $lookup của MongoDB.

Population là quá trình tự động thay thế một path cụ thể bằng các document trong một collection khác. Chúng ta có thể populate một document, nhiều document, một đối tượng, nhiều đối tượng hoặc tất cả các đôi tượng trả về từ một query.

Ví dụ:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const personSchema = new Schema({
_id: Schema.Types.ObjectId,
name: String,
age: Number,
stories: [{ type: Schema.Types.ObjectId, ref: 'Story' }],
});
const storySchema = new Schema({
author: { type: Schema.Types.ObjectId, ref: 'Person' },
title: String,
fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }],
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);

Chúng ta đã tạo hai model. Model Person của chúng ta có trường stories là mảng các ObjectId. Tùy chọn ref cho biết ta muốn Mongoose sử dụng population, trong trường hợp này là đối với model Story. Tất cả _id được lưu trong stories sau khi population sẽ trở thành các document có _id tương ứng trong Story.

Lưu tham chiếu tới các tài liệu khác

const author = new Person({
_id: new mongoose.Types.ObjectId(),
name: 'Ian Fleming',
age: 50,
});
author.save(function (err) {
if (err) return handleError(err);
const story1 = new Story({
title: 'Casino Royale',
author: author._id, // assign the _id from the person
});
story1.save(function (err) {
if (err) return handleError(err);
// that's it!
});
});

Population

Để thực hiện population, sử dụng phương thức populate() của query và truyền vào tên trường cần population:

Story.findOne({ title: 'Casino Royale' })
.populate('author')
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The author is %s', story.author.name);
// prints "The author is Ian Fleming"
});

Khi không có document nào, kết quả populate sẽ là null hoặc mảng trống []

Lọc trường cần populate

Đặt các trường cần lọc trong một mảng và truyền như đối số thứ hai của populate(). Nếu chỉ có một trường, không cần truyền mảng:

Story.findOne({ title: /casino royale/i })
.populate('author', 'name') // only return the Persons name
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The author is %s', story.author.name);
// prints "The author is Ian Fleming"
console.log('The authors age is %s', story.author.age);
// prints "The authors age is null'
});

Population nhiều trường cùng lúc

Story.
find(...).
populate('fans').
populate('author').
exec();

Nhiếu populate nhiều lần trên cùng một path, lần cuối cùng sẽ thắng:

// The 2nd `populate()` call below overwrites the first because they
// both populate 'fans'.
Story.find()
.populate({ path: 'fans', select: 'name' })
.populate({ path: 'fans', select: 'email' });
// The above is equivalent to:
Story.find().populate({ path: 'fans', select: 'email' });

Population theo điều kiện

Story.find()
.populate({
path: 'fans',
match: { age: { $gte: 21 } },
// Explicitly exclude `_id`, see http://bit.ly/2aEfTdB
select: 'name -_id',
})
.exec();

Population một document đã có

Document cũng hợ phương thức populate(), nhưng phải gọi execPopulate để chạy population.

const person = await Person.findOne({ name: 'Ian Fleming' });
person.populated('stories'); // null
// Call the `populate()` method on a document to populate a path.
// Need to call `execPopulate()` to actually execute the `populate()`.
await person.populate('stories').execPopulate();
person.populated('stories'); // Array of ObjectIds
person.stories[0].name; // 'Casino Royale'

Có thể chaining được:

await person.populate('stories').populate('fans').execPopulate();
person.populated('fans'); // Array of ObjectIds

Populate Virtuals

Sử dùng mongoose virtual, bạn có thể dịnh nghĩa mối quan hệ tinh vi hơn giữa các document.

const PersonSchema = new Schema({
name: String,
band: String,
});
const BandSchema = new Schema({
name: String,
});
// member là mảng chứa những document trong
// 'Person' có thuộc tín 'band' bằng thuộc tính
// 'name' trong document hiện tại của BandSchema
BandSchema.virtual('members', {
ref: 'Person', // model cần sử dụng
localField: 'name',
foreignField: 'band',
justOne: false, // members là mảng
options: { sort: { name: -1 }, limit: 5 },
});
const Person = mongoose.model('Person', PersonSchema);
const Band = mongoose.model('Band', BandSchema);

Khi cần kết quả là chuỗi JSON cần thêm tùy chọn virtuals: true trong new Schema():

// Set `virtuals: true` so `res.json()` works
const BandSchema = new Schema(
{
name: String,
},
{ toJSON: { virtuals: true } }
);

Khi chỉ cần đếm số document phù hợp, thêm tùy chọn count: true:

const PersonSchema = new Schema({
name: String,
band: String,
});
const BandSchema = new Schema({
name: String,
});
BandSchema.virtual('numMembers', {
ref: 'Person', // The model to use
localField: 'name', // Find people where `localField`
foreignField: 'band', // is equal to `foreignField`
count: true, // And only get the number of docs
});
// Later
const doc = await Band.findOne({ name: 'Motley Crue' }).populate('numMembers');
doc.numMembers; // 2