猫鼬和人口嵌套

Mongoose and population nested

我读过一些关于 Mongoose 的文章,好像它无法管理嵌套人口。

事实是,当我执行 findOne() 调用时,返回的对象能够使用填充函数。但是好像不行...

你能帮我处理一个 2 级填充吗?多次查询不是问题。

这是我的 CoffeeScript 代码:

Agency.findOne({"agencyUsers": idUser}, '_id agencyUsers agencySites agencyDevices').populate([
      {path: 'agencyUsers', match: { actif: true}, select: '_id userPin'},
      {path: 'agencySites', match: { actif: true}, select: '_id siteName siteBuildings siteSectors siteUsers'},
      {path: 'agencyDevices', match: { actif: true}, select: '_id deviceMac'}
    ])
    .exec((err, res)=>
      if err
        deffered.reject err
      else
        res.populate('agencySites.siteBuildings', (errs, ress)=>
          deffered.resolve(res)
        )
    )

编辑:

根据我们需要在sites(in agency)中填充建筑物

机构架构:

{
  agencyName: String,
  agencyStreet: String,
  agencyCity: String,
  agencyPostal: String,
  agencyCountry: String,
  agencyPhone: String,
  agencyFax: String,
  agencyMail: String,
  agencySiret: String,
  agencyAgencies: [
    { type: Schema.ObjectId, ref: 'Agency' }
  ],
  agencyDevices: [
    { type: Schema.ObjectId, ref: 'Device' }
  ],
  agencySites: [
    { type: Schema.ObjectId, ref: 'Site' }
  ],
  agencyUsers: [
    { type: Schema.ObjectId, ref: 'User' }
  ],
  agencyModules: [
    { type: Schema.ObjectId, ref: 'Module' }
  ],
  actif: {type: Boolean, default: true},
  actif_date: { type: Date, default: null },
  creation_date: { type: Date, default: Date.now },
  edit_date: { type: Date, default: null }
}

网站架构:

{
  siteName: String,
  siteStreet: String,
  siteCity: String,
  sitePostal: String,
  siteCountry: String,
  sitePhone: String,
  siteMail: String,
  siteAgencies: [
    { type: Schema.ObjectId, ref: 'Agency' }
  ],
  siteUsers: [
    { type: Schema.ObjectId, ref: 'User' }
  ],
  siteSectors: [
    { type: Schema.ObjectId, ref: 'Sector' }
  ],
  siteBuildings: [
    { type: Schema.ObjectId, ref: 'Building' }
  ],
  siteModules: [
    { type: Schema.ObjectId, ref: 'Module' }
  ],
  actif: {type: Boolean, default: true},
  actif_date: { type: Date, default: null },
  creation_date: { type: Date, default: Date.now },
  edit_date: { type: Date, default: null }
}

建筑架构:

{
  buildingName: String,
  buildingFloors: [
    { type: Schema.ObjectId, ref: 'Floor' }
  ],
  actif: {type: Boolean, default: true},
  actif_date: { type: Date, default: null },
  creation_date: { type: Date, default: Date.now },
  edit_date: { type: Date, default: null }
}

JavaScript 仅响应。你做 translation :)

由于我目前无法理解的实际原因,这在这种情况下实际上不起作用,但为了做你想做的事,你需要调用 .populate() 的 "Model" 形式:

Agency.findOne({"agencyUsers": idUser}, '_id agencyUsers agencySites agencyDevices')
    .populate([
      { "path": "agencyUsers", "match": { "actif": true}, "select": "_id userPin" },
      {
        "path": "agencySites", 
        "match": { "actif": true }, 
        "select": "_id siteName siteBuildings siteSectors siteUsers"
      },
      {
        "path": "agencyDevices", 
        "match": { "actif": true}, 
        "select": "_id deviceMac"
      }
]).exec(function(err,doc) {
    if (err) {
        deffered.reject(err);
    } else {
        async.waterfall([
           function(callback) {
               Agency.populate( doc, { "path": "agencyAgencies.siteAgencies" },callback);
           },
           function(doc,callback) {
               User.populate( doc, { "path": "agencyAgencies.siteUsers" },callback);
           },
           function(doc,callback) {
               Sector.populate( doc, { "path": "agencyAgencies.siteSectors" },callback);
           },
           function(doc,callback) {
               Building.populate( doc, { "path": "agencyAgencies.siteBuildings" },callback);
           },
           function(doc,callback) {
               Module.populate( doc, { "path": { "agencyAgencies.siteModules" },callback);
           }
        ],function(err,res) {
           if (err) {
               deffered.reject(err);
           } else {
               Floor.populate(res,{ "path": "agencyAgencies.siteBuildings.buildingFloors" },function(err,res) {
                   if (err) {
                       deffered.reject(err);
                   } else {
                       deffered.resolve(res);
                   }
               });
           }
        });
    }
});

尽管我使用了 "async.waterfall",但我只是试图通过每次嵌入式迭代来避免 "indentation creep"。

因此,正如您所见,实际上有必要为每个特定模型类型以这种方式调用 .populate()。 "again nested" "Building" 模型引用中也有必要的调用,在填充 "that" 文档后也需要调用。因此,"call stack" 是您之前提到的嵌套填充问题的一部分。

对于如此大量的引用,您最好考虑重新设计架构 and/or 以嵌入大量此类数据。它看起来非常 "relational",因此您可能无法通过这种方式获得全部 MongoDB 好处。

@Neil Lunn 的回答是正确的,我只是想说明一下 co 以防你有兴趣清理你的代码:

co(function* {
  var agency = yield Agency.findOne({'agencyUsers': idUser}, '_id agencyUsers agencySites agencyDevices')
    .populate(
      { 'path': 'agencyUsers', 'match': { 'actif': true }, 'select': '_id userPin' },
      { 'path': 'agencySites', 'match': { 'actif': true }, 'select': '_id siteName siteBuildings siteSectors siteUsers' },
      { 'path': 'agencyDevices', 'match': { 'actif': true }, 'select': '_id deviceMac' })

  agency = yield Agency.populate(agency, 'agencyAgencies.siteAgencies')
  agency = yield User.populate(agency, 'agencyAgencies.siteUsers')
  agency = yield Sector.populate(agency, 'agencyAgencies.siteSectors')
  agency = yield Building.populate(agency, 'agencyAgencies.siteBuildings')
  agency = yield Module.populate(agency, 'agencyAgencies.siteModules')

  return agency
})
.then(agency => {
   Floor.populate(agency, 'agencyAgencies.siteBuildings.buildingFloors', function (err, res) {
     if (err)
       deferred.reject(err)

     deferred.resolve(agency)
   })
})
.catch(deferred.reject)

从版本^4.1.x开始,深度人口实际上是猫鼬的东西,所以这样做是有效的:-

Post.find({}).populate({
   path: 'user',
   select: 'id name',
   populate: {
      path: 'friends',
      select: 'id name,'
      populate: {
          path: 'friends',
          select: 'id name',
      }
   }
})

populate 可以将对象或对象数组作为值,这意味着您可以填充引用不同集合的同级属性。 有关详细信息,请查看此 post:http://frontendcollisionblog.com/mongodb/2016/01/24/mongoose-populate.html