Ссылки на документы Mongoose с отношением «один ко многим»

Я работаю над проектированием структуры базы данных для нового проекта, и я новичок в MongoDB и, очевидно, в Mongoose.

Я читал документацию Mongooses по населению, где она имеет отношение "один ко многим", с одним Person со многими Story документами, но меня смущает то, что вместо Story документов, ссылающихся на то, какому Person документу он принадлежит, схема Person настроила его таким образом, что у него есть массив Story документов, которыми он «владеет».

Я настраиваю что-то очень похожее на это. Но я продолжаю думать, что при создании новых Story документов было бы проще иметь идентификатор документа Person. Но, возможно, это только потому, что я лучше знаком с отношениями MySQL, использующими соединения.

Если это лучший способ сделать это (а я уверен, что это так, поскольку он есть в документах), когда создаются новые Story документы, как лучше всего обновить массив историй в связанном People документе, которому он принадлежит? ? Я посмотрел, но не смог найти примеров обновления существующих документов для добавления ссылок на другие документы (или их удаления, если на то пошло)

Я уверен, что это простое решение, которое я просто упустил из виду или что-то в этом роде, но любая помощь была бы отличной. Спасибо!


person Justin    schedule 25.01.2016    source источник
comment
Какое обновление вы хотите?   -  person Manasov Daniel    schedule 25.01.2016
comment
Я не знал, что существует более одного типа обновления? Я пытаюсь обновить их с помощью моделей Mongoose, но я предполагаю, что это не то, что вы имеете в виду.   -  person Justin    schedule 25.01.2016
comment
не могли бы вы сказать, что именно вы хотите сделать? вставить в массив личных историй, удалить что-то, изменить каждую историю или что-то еще?   -  person Manasov Daniel    schedule 25.01.2016
comment
В этой ситуации создайте новую историю, а затем добавьте новую историю к элементу Person.stories.   -  person Justin    schedule 25.01.2016


Ответы (5)


Обратитесь к населению, здесь извлеките пример из Mongoose.

var mongoose = require('mongoose')
, Schema = mongoose.Schema

var personSchema = Schema({
  _id     : Schema.Types.ObjectId,
  name    : String,
  age     : Number,
  stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
});

var storySchema = Schema({
  _creator : { type: Schema.Types.ObjectId, ref: 'Person' },
  title    : String,
  fans     : [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});

var Story  = mongoose.model('Story', storySchema);
var Person = mongoose.model('Person', personSchema);

Итак, в примере Story модель хранит связанные Person._id в Story._creator. Когда вы найдете документ Story, вы можете использовать метод populate(), чтобы определить, какой атрибут в модели Person вы хотите получить одновременно, например:

Story.findOne({_id: 'xxxxxxx'}).populate('person', 'name age').exec(function(err, story) {
  console.log('Story title: ', story.title);
  console.log('Story creator', story.person.name);
});

Я считаю, что это то, что вы ищете. Или же вы можете использовать вложенные коллекции вместо.

person Keiran Tai    schedule 25.01.2016
comment
Итак, Story._creator будет автоматически ссылаться на родителя Person._id? Или вам нужно обновить значения Story._creator и Person.stories по отдельности? Что меня на самом деле больше беспокоит, так это когда вы создаете новую историю. Какой лучший способ обновить массив родителей stories? Есть ли способ подтолкнуть к нему новый идентификатор с помощью одного запроса? Или вам нужно запросить весь документ, затем поместить новую историю в массив Person.stories, а затем обновить документ Person? - person Justin; 25.01.2016
comment
Нужно ли обновлять значения Story._creator и Person.stories по отдельности? Или есть способ автоматически заполнить Person.stories при создании нового документа Story? - person Justin; 25.01.2016
comment
Да, вам нужно обновить оба. Раздел заполнение мангуста о ссылках на детей - person Manasov Daniel; 25.01.2016
comment
Меня беспокоит возможность их рассинхронизации, если у меня есть другие сценарии, работающие с этой базой данных, или если кто-то возится с ней вручную. - person Justin; 25.01.2016
comment
Вы можете использовать $push для обновления историй и управления ими. Также может помочь документ atomicity. - person Keiran Tai; 25.01.2016
comment
Вам следует подумать об исправлении опечаток в этом ответе. Нет модели Store или Store._creator — есть Story, и это сбивает с толку, потому что вы также используете фактическое хранилище слов. - person Jacob Beauchamp; 08.07.2017
comment
Спасибо @JacobBeauchamp - person Keiran Tai; 28.07.2017
comment
Есть ли причина, по которой вы изменили Person._id на число, а не на то, что в документах указано как Schema.Types.ObjectId? - person Isaac Pak; 10.01.2019
comment
@IsaacPak Я не могу вспомнить почему, это было давно. Вероятно, старый документ от Mongoose тоже использует Number, т.е. .x/docs/populate.html - person Keiran Tai; 14.01.2019

Предыдущие ответы на этот вопрос были полезны, но может быть полезно увидеть более подробный код. Приведенный ниже код взят из моего бэкэнда Express.js для моего приложения. Мое приложение позволяет пользователям писать отзывы. При запросе пользователя я возвращаю все отзывы, которые сделал пользователь.

user_model.js

import mongoose, { Schema } from 'mongoose';


const UserSchema = new Schema({
  firstname: String,
  lastname: String,
  username: { type: String, unique: true },
  reviews: [{ type: Schema.Types.ObjectId, ref: 'Review' }],
}, {
  toJSON: {
    virtuals: true,
  },
});

const UserModel = mongoose.model('User', UserSchema);
export default UserModel;

обзор_модель.js

import mongoose, { Schema } from 'mongoose';

const ReviewSchema = new Schema({
  body: String,
  username: String,
  rating: Number,
}, {
  toJSON: {
    virtuals: true,
  },
});

const ReviewModel = mongoose.model('Review', ReviewSchema);
export default ReviewModel;

Review_controller.js

// . . .
export const createReview = (req, res) => {
    const review = new Review();
    review.username = req.body.username;
    review.rating = req.body.rating;
    review.body = req.body.body;
    review.save()
      .then((result) => {
        User.findOne({ username: review.username }, (err, user) => {
            if (user) {
                // The below two lines will add the newly saved review's 
                // ObjectID to the the User's reviews array field
                user.reviews.push(review);
                user.save();
                res.json({ message: 'Review created!' });
            }
        });
      })
      .catch((error) => {
        res.status(500).json({ error });
      });
};

user_controller.js

 export const createUser = (req, res) => {
   const user = new User();
   user.username = req.body.username;
   user.email = req.body.email;
   user.save()
       .then((result) => {
            res.json({ message: 'User created!', result });
        })
        .catch((error) => {
          res.status(500).json({ error });
        });
    };

// . . .
// returns the user object associated with the username if any
// with the reviews field containing an array of review objects 
// consisting of the reviews created by the user
export const getUser = (req, res) => {
    User.findOne({ username: req.params.username })
      .populate('reviews')
      .then((result) => {
        res.json(result);
      })
      .catch((error) => {
        res.status(500).json({ error });
      });
  };
person College Student    schedule 19.05.2019
comment
Такое прекрасное объяснение! Спасибо - person Yashwardhan Pauranik; 02.10.2019
comment
Спасибо за подробный ответ. Не могли бы вы опубликовать остальную часть кода? Я также хочу знать, как вы добавили пользователей. Спасибо - person Eid; 17.11.2019
comment
@Eid, я добавил свою функцию createUser. Это помогает? В противном случае вы можете найти код моего файла user_controller.js здесь hastebin.com/egesasutuk.coffeescript - person College Student; 20.11.2019
comment
При этом в праве пользователя сохраняются только идентификаторы обзора, но не полные данные? - person Biku7; 19.07.2021

Как указано в документах о населении

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!
  });
  //then add story to person
  aaron.stories.push(story1);
  aaron.save(callback);
});
person Manasov Daniel    schedule 25.01.2016
comment
Если story1 удалили, то нужен ли aaron.stories.pop(story1)? - person William Hu; 27.05.2019
comment
Можно ли пакетно заполнить родительский массив результатом запроса? т.е. истории = Story.find({имя}) aaron.stories = истории - person FluffyBeing; 12.06.2019

Односторонняя или двусторонняя связь

Есть еще одна возможность, о которой вы могли бы подумать: вам действительно нужна двусторонняя связь? Или было бы достаточно хранить только _creator в каждом Story. И не сохраняйте list of stories для каждого Person. Список историй по-прежнему можно запросить в поиске:

let allStoriesOfOneCreator = Stories.find({_creator: person._id});

https://docs.mongodb.com/manual/tutorial/model-referenced-one-to-many-relationships-between-documents/

В конце концов, это зависит от требований вашего приложения. Как часто вам нужны истории создателя?

person Robert    schedule 24.08.2020

// если вы являетесь пользователем скрипта типа, то:

import mongoose from 'mongoose';

interface PromotionAttrs {
  price: number;
  startDate: Date;
  endDate: Date;
}

export interface PromotionDoc extends mongoose.Document {
  price: number;
  startDate: string;
  endDate: string;
}

interface PromotionModel extends mongoose.Model<PromotionDoc> {
  build(attrs: PromotionAttrs): PromotionDoc;
}

const promotionSchema = new mongoose.Schema({
  price: {
    type: Number,
  },
  startDate: {
    type: mongoose.Schema.Types.Date,
  },
  endDate: {
    type: mongoose.Schema.Types.Date,
  },
});

promotionSchema.statics.build = (attrs: PromotionAttrs) => {
  return new Promotion(attrs);
};

const Promotion = mongoose.model<PromotionDoc, PromotionModel>(
  'Promotion',
  promotionSchema
);

export { Promotion };
import mongoose from 'mongoose';
import { PromotionDoc } from './promotion';

interface ProductAttrs {
  name: string;
  promotions?: PromotionDoc[];
}

interface ProductModel extends mongoose.Model<ProductDoc> {
  build(attrs: ProductAttrs): any;
}
interface ProductDoc extends mongoose.Document {
  name: string;
  promotions?: PromotionDoc[];
}
const productSchema = new mongoose.Schema({
  promotions: [
    {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Promotion',
    },
  ],
});

productSchema.statics.build = (attrs: ProductAttrs) => {
  return new Product(attrs);
};
const Product = mongoose.model<ProductDoc, ProductModel>(
  'Product',
  productSchema
);

export { Product };
const product = await Product.findById(productId);

    if (!product) {
      throw new NotFoundError();
    }
const promotion = Promotion.build({
        price,
        startDate,
        endDate,
      });
      await promotion.save();
      product.promotions?.push();
      await product.save();
person Lord    schedule 09.07.2021