首页 > 解决方案 > Mongoose 多对多关系

问题描述

我是 mongoDB 和 Mongoose 的新手,我在关系方面遇到了一些问题。我的架构有 3 个表(用户/个人/家庭),您可以在下面看到它。

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

var userSchema = Schema({
    _id       : Schema.Types.ObjectId,
    email     : String,
    person    : [{ type: Schema.Types.ObjectId, ref: 'Person' }] // A user is linked to 1 person
});

var personSchema = Schema({
    _id       : Schema.Types.ObjectId,
    name      : String,
    user      : [{ type: Schema.Types.ObjectId, ref: 'User' }]   // A person is linked to 1 user
    families  : [{ type: Schema.Types.ObjectId, ref: 'Family' }] // A person have 1,n families
});

var familySchema = Schema({
    _id       : Schema.Types.ObjectId,
    name      : String,
    persons   : [{ type: Schema.Types.ObjectId, ref: 'Person' }] // A family have 0,n persons
});

var User   = mongoose.model('User', userSchema);
var Person = mongoose.model('Person', personSchema);
var Family = mongoose.model('Family', familySchema);

我不知道我的架构是否良好,我的参数person是否需要userSchema?因为信息将被复制,userSchema所以我将拥有 personID,而personSchema这将是 userID。

如果我理解为我的请求提供此重复值很有用?但如果信息重复,我需要执行两个查询来更新两个表?

例如,如果我有一个人有一个家庭(personSchema 中的 families 参数),并且在这个家庭中我有这个人(familySchema 中的 persons 参数)。删除/更新表格中的行的请求是什么?

谢谢

标签: node.jsmongodbmongoosemongoose-schemamongoose-populate

解决方案


恕我直言,如果您的架构满足您的需求,它看起来不错!(虽然,如果您认为您当前的模式可以满足您的目的而不会臃肿,那么是的,它很好)..

  1. “人”似乎是唯一类型的用户和与其他实体连接的实体。只要是这种情况,您就可以随意从用户模式中删除人员参数,因为您可以访问人员的用户信息。但是让我们假设如果存在另一个实体“Aliens”,他们也有自己独特的家庭,那么最好在“User”Schema 中添加alien 和 person 参数以查看用户的类型。(只要只有一种类型,即人,那么您可能不需要在用户模式中添加它)。如果您仍然想保留它,那么请在您传递数组时也在您的架构中进行以下更改,尽管它似乎是一对一的关系!

var userSchema = Schema({
  _id       : Schema.Types.ObjectId,
  email     : String,
  person    : { type: Schema.Types.ObjectId, ref: 'Person' } // A user is linked to 1 
  //person // Here I have removed the []
});

var personSchema = Schema({
_id       : Schema.Types.ObjectId,
name      : String,
user      : { type: Schema.Types.ObjectId, ref: 'User' }   // removed [] here too
families  : [{ type: Schema.Types.ObjectId, ref: 'Family' }] 
});

  1. 是的,如果您想保持一致性,您将需要为实体 Person 和 Family 更新它。但是,它可以在一个请求/突变中完成。

  2. 好吧,您可以根据业务逻辑的流顺序执行请求。假设“荷马”是辛普森家族的新成员。因此,在这种情况下,您可以将“Homer”添加到 Family 集合(表)中,然后将此 Simpson(Family 集合)的 ObjectId 推送到Person实体。

我在下面添加了将 Homer 添加到 Simpson 家族的示例。希望这可以帮助 :)

addNewFamilyMember: async (_, {personID, familyID}) => {
  try{
    let family = await Family.findOne({_id: familyID});
    let person = await Person.findOne({_id: personID}); // created to push the objectId of the family in this
    if (!family){
      throw new Error ('Family not found !')
    } else {
      let updatedFamily = await Family.findByIdAndUpdate(
        { _id: familyID },
        {
          "$addToSet": { // "addToSet" inserts into array only if it does not exist already
            persons: personID
          }
        },
        { new: true }
      );

      person.families.push(updatedFamily); // pushing objectId of the Simpson family in Person "Homer"
      person = await person.save();
      updatedFamily.persons.push(person); // pushing "Homer" in the Simpson family
      updatedFamily = updatedFamily.save();
      
      return updatedFamily;
    }
  } catch(e){
  throw e;
  }
}

如果要执行更新,则取决于您的目的的意图(例如,如果您只想更新名称“Homer”,则只需在 Person 集合中更新它,因为 Family 集合已经引用了 Homer 的 objectId,因此每次对 Homer 进行更新时,更新后的文档都会被 Family 集合引用!),并且

如果您想执行删除,那么在这种情况下,方法也会根据场景而有所不同,就像您希望删除人员文档,或者只是从家庭中删除人员参考,或者从家庭中删除家庭参考人 !!

假设您想删除一个人,那么在这种情况下,您必须获取 personId 并搜索该人,并且由于您可以通过此人访问家庭,您可以通过 person.families 访问家庭并删除 personId也来自那些各自的家庭!然后你也可以删除关联的用户,因为你也从同一个 person 对象中引用了用户。

总而言之,这取决于您选择的操作,以及您希望在架构中进行多少清理。如果我们采用不同的方法,上述过程将有所不同。


推荐阅读