简体   繁体   中英

How to reference and populate nested schemas?

TL;DR How do you reference (and thus populate) subdocuments within the same collection?

I've been trying for a while now to populate a reference to a subdocument in my Mongoose schema. I have a main schema (MainSchema) which holds arrays of locations and contacts. The locations have a reference to these contacts.

In my location array i make a reference to these contacts by the _id of the contact. See below.

import mongoose from 'mongoose';

const LocationSchema = new mongoose.Schema({
  city: {type: String},
  contact: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Contact' //alternative tried: refPath: 'contacts'
  } 
});
const Location = mongoose.model('Location', LocationSchema);

const ContactSchema = new mongoose.Schema({
  firstName: {type: String},
  lastName: {type: String}
});
const Contact = mongoose.model('Contact', ContactSchema );

const MainSchema = new mongoose.Schema({
  name: {type: String},
  locations: [LocationSchema],
  contacts: [ContactSchema]    
});

export.default mongoose.model('Main', 'MainSchema');

Now when i want to populate the contact of the locations I get null or just the plain _id string returned. Below is my populate code. I've tried every combination i could find, involving making the nested documents their own models and trying different ways to reference them.

MainSchema.statics = {
  get(slug) {
    return this.findOne({name})
      .populate('locations.contact')
      .exec()
      .then((company) => {
        if (company) {
          return company;
        }
        const err = 'generic error message';
        return Promise.reject(err);
      });
    }
  };

I've also tried the newer approach to no avail:

populate({
  path: 'locations',
  populate: {
    path: 'contacts',
    model: 'Contact'
  }
});

I must be missing something here. But what?

edited the question to show the complete query statement as requested

So taking your example of schema I would do the following. Please take notice that I don't say that my approach is the best way of doing this, but I had an exact similar case as you.

Mongoose model

import mongoose from 'mongoose';

const LocationSchema = new mongoose.Schema({
    city: {type: String},
    contact: { type: Schema.Types.ObjectId, ref: 'Contact'}
});

const ContactSchema = new mongoose.Schema({
    firstName: {type: String},
    lastName: {type: String}
});


const MainSchema = new mongoose.Schema({
    name: {type: String},
    locations: [{ type: Schema.Types.ObjectId, ref: 'Location' }],
});

const Main = mongoose.model('Main', MainSchema);
const Location = mongoose.model('Location', LocationSchema);
const Contact = mongoose.model('Contact', ContactSchema );

Note: In your main schema I've removed the contacts since I understand from your example that each location has it's own contact person, so actually in the MainSchema you don't need the ContactSchema

The controller where the you insert the data

The idea here is that you have to pass the reference _id from each document to the other one, the below example is mock-up please adapt it to fit your app . I used a data object that I assume that has one location and one person contact

async function addData(data) {

    //First you create your Main document
    let main = await new Main({
        name: data.name
    }).save();

    //Create the contact document in order to have it's _id to pass into the location document
    let contact = await new Contact({
        firstName: data.fistName,
        lastName: data.lastName
    });

    //Create the location document with the reference _id from the contact document
    let location = await new Location({
        city: data.city,
        contact: contact._id
    }).save();

    //Push the location document in you Main document location array
    main.locations.push(location);
    main.save();

    //return what ever you need
    return main;
}

Query

let mainObj = await Main.findOne({name})
    .populate({path: 'locations', populate: {path: 'contact'}})
    .exec();

This approach is working for me, hope it serves you as well

After searching some more i found an exact same case posted as an issue on the Mongoose github issue tracker .

According to the prime maintainer of Mongoose this form of populate is not possible:

If you're embedding subdocs, you're not going to be able to run populate() on the array, because the subdocs are stored in the doc itself rather than in a separate collection.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM