Mongoose之population

There are no joins in MongoDB but sometimes we still want references to documents in other collections. This is where population comes in.

Population is the process of automatically replacing the specified paths in the document with document(s) from other collection(s). We may populate a single document, multiple documents, plain object, multiple plain objects, or all objects returned from a query. Let’s look at some examples.

MongoDB中没有连接,但有时候我们仍然需要引用其他集合的文档;这就是population
Population是自动的将文档准确的路径替换为来自其他集合的文档的过程,我们可能会填充单个文档,多个文档,普通的对象,多个普通的对象或者所有的从查询中返回的对象。让我们来看写例子吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var mongoose = require('mongoose'),
Schema = mongoose.Schema
var personSchema = Schema({
_id : Number,
name : String,
age : Number,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});
var storySchema = Schema({
_creator : { type: Number, ref: 'Person' },
title : String,
fans : [{ type: Number, ref: 'Person' }]
});
var Story = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);

So far we’ve created two Models. Our Person model has it’s stories field set to an array of ObjectIds. The ref option is what tells Mongoose which model to use during population, in our case the Story model. All _ids we store here must be document _ids from the Story model. We also declared the Story _creator property as a Number, the same type as the _id used in the personSchema. It is important to match the type of _id to the type of ref.

Note: ObjectId, Number, String, and Buffer are valid for use as refs.

我们已经创建了两个Models,我们的Person模型有它自己的stories字段用来设置成ObjectIds数组。ref选项是告诉Mongoose在填充的过程中要使用哪一个model,在这个例子中的model是Story,我们仍然可以将Story的_creator属性声明为Number类型,与personSchema中使用的_id是相同的类型。匹配_id与ref的类型是很重要的一件事。

提醒: ObjectId, Number, String, 和Buffer类型对refs来说都是有效的

Saving refs

Saving refs to other documents works the same way you normally save properties, just assign the _id value:

将引用保存到其它文档与你通常保存属性的方式相同,只需分配_id的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var aaron = new Person({ _id: 0, name: 'Aaron', age: 100 });
aaron.save(function (err) {
if (err) return handleError(err);
var story1 = new Story({
title: "Once upon a timex.",
_creator: aaron._id // assign the _id from the person
});
story1.save(function (err) {
if (err) return handleError(err);
// thats it!
});
})

Population

So far we haven’t done anything much different. We’ve merely created a Person and a Story. Now let’s take a look at populating our story’s _creator using the query builder:

到现在为止我们没有做太多任何不同的事情,仅仅只是创建了Person和Story,让我们来看看使用查询构建器来填充Story中的_creator.

1
2
3
4
5
6
7
8
9
Story
.findOne({ title: 'Once upon a timex.' })
.populate('_creator')
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The creator is %s', story._creator.name);
// prints "The creator is Aaron"
})

Populated paths are no longer set to their original _id , their value is replaced with the mongoose document returned from the database by performing a separate query before returning the results.

Arrays of refs work the same way. Just call the populate method on the query and an array of documents will be returned in place of the original _ids.

Note: mongoose >= 3.6 exposes the original _ids used during population through the document#populated() method.

补全路径不再是设置其原始的_id,他们的值是在返回结果之前被执行单独查询后从数据库中返回的mongoose文档所代替(google:通过在返回结果之前执行单独的查询,将其值替换为从数据库返回的mongoose文档。

refs的数组也是同样工作的,仅仅是在查询中调用populate的方法,将返回文档数组来代替原始的_id。

提示3.6以上的版本通过populated方法暴露在填充时使用的原始_id。

Field selection(字段选择)

What if we only want a few specific fields returned for the populated documents? This can be accomplished by passing the usual field name syntax as the second argument to the populate method:

如果我们只想为填充的文档返回几个特定的字段该怎么办?这能够通过传递通常的字段来作为populate方法的第二个参数来实现。

1
2
3
4
5
6
7
8
9
10
11
12
Story
.findOne({ title: /timex/i })
.populate('_creator', 'name') // only return the Persons name
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The creator is %s', story._creator.name);
// prints "The creator is Aaron"
console.log('The creators age is %s', story._creator.age);
// prints "The creators age is null'
})

Populating multiple paths(填充多个路径)

如果我们要同时填充多个路径怎么办?

1
2
3
4
Story
.find(...)
.populate('fans author') // space delimited path names
.exec()

In mongoose >= 3.6, we can pass a space delimited string of path names to populate. Before 3.6 you must execute the populate() method multiple times.

在3.6以上的版本中,我们能通过空格分隔的路径名字符串来填充,3.6以前你必须多次执行populate()方法。

1
2
3
4
5
6
Story
.find(...)
.populate('fans')
.populate('author')
.exec()

Query conditions and other options(查询条件和其他选项)

如果我们想要基于年龄来填充fans数组,只选择他们的名字并返回他们当中的5个怎么办?

1
2
3
4
5
6
7
8
9
Story
.find(...)
.populate({
path: 'fans',
match: { age: { $gte: 21 }},
select: 'name -_id',
options: { limit: 5 }
})
.exec()

Refs to children

We may find however, if we use the aaron object, we are unable to get a list of the stories. This is because no story objects were ever ‘pushed’ onto aaron.stories.

There are two perspectives here. First, it’s nice to have aaron know which stories are his.

我们可能会发现,如果使用arron这个对象,我们将不会拿到stories的列表。这是因为没有story对象被放入arron.stories中。

1
2
3
aaron.stories.push(story1);
aaron.save(callback);

This allows us to perform a find and populate combo:

这就执行查找和填充组合。

1
2
3
4
5
6
7
8
Person
.findOne({ name: 'Aaron' })
.populate('stories') // only works if we pushed refs to children
.exec(function (err, person) {
if (err) return handleError(err);
console.log(person);
})

It is debatable that we really want two sets of pointers as they may get out of sync. Instead we could skip populating and directly find() the stories we are interested in.

有争议的是,我们真的需要两组指针因为他们可能不同步。相反的,我们能跳过填充,直接查找我们要的stories。

1
2
3
4
5
6
Story
.find({ _creator: aaron._id })
.exec(function (err, stories) {
if (err) return handleError(err);
console.log('The stories are an array: ', stories);
})

Updating refs

Now that we have a story we realized that the _creator was incorrect. We can update refs the same as any other property through Mongoose’s internal casting:

现在我们有一个story并且意识到它的_creator属性是错误的,我们能通过Mongoose的内部转换来更新refs就想操作其他任何属性一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var guille = new Person({ name: 'Guillermo' });
guille.save(function (err) {
if (err) return handleError(err);
story._creator = guille;
console.log(story._creator.name);
// prints "Guillermo" in mongoose >= 3.6
// see https://github.com/LearnBoost/mongoose/wiki/3.6-release-notes
story.save(function (err) {
if (err) return handleError(err);
Story
.findOne({ title: /timex/i })
.populate({ path: '_creator', select: 'name' })
.exec(function (err, story) {
if (err) return handleError(err);
console.log('The creator is %s', story._creator.name)
// prints "The creator is Guillermo"
})
})
})