mongoose 文档(七) Population

在mongoose中没有join但有时我们仍然想引用其他collection的document,population由此而来。

 

population是自动将document中指定path替换为其他collection的document的过程。我们能迁移document、多个document、简单对象、多个简单对象或者是查询返回的所有对象。

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);

 到目前为止我们已经创建了2个Model。Person model的stories域已经设置到一个ObjectIds数组。ref选项告诉mongoose在填充的时候使用哪个Model,在我们的例子是Story model。我们存储在这里的所有 _id 必须是来自 Story Model 的document _id。我们同时还声明了 Story _creator 属性为Number,和在personSchema里的_id的类型一样。匹配_id和ref的类型非常重要。

注意: ObjectId, Number, String, 和 Buffer可以用来作为 ref。

 

 

保存 ref

保存对于其他document的ref的工作方式和保存属性相同,只是分配_id值。

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

 到目前为止,我们还没有做什么太大的不同。我们只是创造了一个Person,一个Story。现在,让我们来看看使用查询生成器填充我们的story’s_creator:

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"
});

填充的路径不再设置到原来的_id,在返回结果之前通过单独查询它们的值将会被从数据库返回的mongoose document取代。

 

ref数组的工作方式相同。在query上调用populate方法就会返回一个document数组取代原来的_ids。

 

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

 

 

Setting Populated Fields

在Mongoose >= 4.0,你也可以手动填充一个域。

Story.findOne({ title: 'Once upon a timex.' }, function(error, story) {
  if (error) {
    return handleError(error);
  }
  story._creator = aaron;
  console.log(story._creator.name); // prints "Aaron"
});

 

注意这只对单个ref有效。目前不能手动填充ref数组。

 

 

Field 选择

如果我们只想要一些填充document的特定的字段?这能通过传递一般的字段名语法作为第二个参数给populate方法来完成。

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

 

 

填充多个 path

如果我们想同时填充多个path怎么办?

Story
.find(...)
.populate('fans _creator') // 用空格分隔 path 名
.exec()

 

在 mongoose >= 3.6,我们能传递一个由空格分隔的字符串组成的path名给populate。在3.6之前你必须多次执行populate() 方法。

Story
.find(...)
.populate('fans')
.populate('_creator')
.exec()

 

 

查询条件和其他选项

如果我们想根据年龄填充fans数组,只选择他们的名字并返回最多5个人呢?

Story
.find(...)
.populate({
  path: 'fans',
  match: { age: { $gte: 21 }},
  select: 'name -_id',
  options: { limit: 5 }
})
.exec()
  • path: 以空格分隔的引用字段的名称
  • select: 填充引用 document 中的哪些字段
  • match: 可选,指定附加的查询条件
  • model: 可选,指定引用的 model
  • options: 可选,指定附加的其他查询选项,如排序以及条数限制等等

 

 

Refs to children

如果我们使用aaron对象,我们不能获得stories列表。这是因为还没有story对象被‘压栈’到 aaron.stories。

这里有两种观点。首先,让aaron知道哪个stories是他的就好了。

aaron.stories.push(story1);
aaron.save(callback);

 

这使我们能够执行一个查找和填充组合:

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

值得商榷的是我们是否要两套指针,因为他们可能会不同步。相反,我们能跳过填充并准确地 find()我们感兴趣的story。

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

 

 

修改 ref

现在,我们有一个story的_creator不正确。我们可以就像其它mongoose属性设置一样修改refs:

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/Automattic/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"
    })
  })
})

 从query population返回的document变为功能齐全,可删除的、可保存的document,除非lean选项被指定。要将它们和子文档搞混了。当调用它的删除方法时要小心,因为你将从数据库中删除它,而不仅仅是数组。

 

 

填充现有document

如果我们有一个现有的mongoose document并想填充它的一些path,mongoose >= 3.6 支持document#populate()方法。

 

 

填充多个现有document

如果我们有一个或多个文件,甚至是简单的对象(像mapReduce 输出),我们可以在 mongoose >= 3.6 使用Model.populate()方法来填充它们。document#populate() 和 query#populate() 就是用这个方法更新document。

 

 

Populating across multiple levels

你有一个记录着用户好友的user schema。

var userSchema = new Schema({
  name: String,
  friends: [{ type: ObjectId, ref: 'User' }]
});

填充让你获得用户的好友列表,但如果你想要用户的朋友的朋友呢?指定populate选项来告诉mongoose以用户所有的朋友来填充friend数组:

User.
  findOne({ name: 'Val' }).
  populate({
    path: 'friends',
    // Get friends of friends - populate the 'friends' array for every friend
    populate: { path: 'friends' }
  });

 

 

Populating across Databases

比如说你有一个代表event的shema和一个代表conversation的shema。每一个event都有一个对应的conversation线程。

var eventSchema = new Schema({
  name: String,
  // The id of the corresponding conversation
  // Notice there's no ref here!
  conversation: ObjectId
});
var conversationSchema = new Schema({
  numMessages: Number
});

同时,假设event和conversation存储在单独的MongoDB实例。

var db1 = mongoose.createConnection('localhost:27000/db1');
var db2 = mongoose.createConnection('localhost:27001/db2');

var Event = db1.model('Event', eventSchema);
var Conversation = db2.model('Conversation', conversationSchema);

在这种情况下,你将无法正常populate()。因为populate()不知道使用哪个model,conversation 域永远是空。可是,你能确指定该mode

Event.
  find().
  populate({ path: 'conversation', model: Conversation }).
  exec(function(error, docs) { /* ... */ });

这可被称为“跨数据库填充”,因为它使你能够通过MongoDB数据库甚至是通过MongoDB实例填充。

 

posted on 2016-02-13 23:41  Surahe  阅读(768)  评论(0编辑  收藏  举报