使用 Mongoose 进行多对多映射

Posted

技术标签:

【中文标题】使用 Mongoose 进行多对多映射【英文标题】:Many-to-many mapping with Mongoose 【发布时间】:2012-06-22 11:44:23 【问题描述】:

我的设计中有 FlashcardSchemas 和 PackageSchemas。一个抽认卡可以属于不同的包,一个包可以包含不同的抽认卡。

您可以在下面看到我的猫鼬模式定义的精简版:

// package-schema.js
var Schema = mongoose.Schema,
    ObjectId = Schema.ObjectId;

var PackageSchema = new Schema(
    id          : ObjectId,
    title       :  type: String, required: true ,
    flashcards  : [ FlashcardSchema ]
);

var exports = module.exports = mongoose.model('Package', PackageSchema);

// flashcard-schema.js
var Schema = mongoose.Schema,
    ObjectId = Schema.ObjectId;

var FlashcardSchema = new Schema(
    id      : ObjectId,
    type        :  type: String, default: '' ,
    story       :  type: String, default: '' ,
    packages    : [ PackageSchema ]
);

var exports = module.exports = mongoose.model('Flashcard', FlashcardSchema);

从上面的cmets可以看出,这两个schema定义属于不同的文件,相互引用。

我收到一个异常,指出未按预期定义 PackageSchema。如何映射与 mongoose 的多对多关系?

【问题讨论】:

没有直接的方法可以做到这一点 - 为什么您将包作为抽认卡模式的一部分,而将抽认卡作为包模式的一部分?您希望运行哪些查询? 当我从数据库中拉出一个包时,我想填充卡片数组,当我从数据库中拉出一张卡时,我想查看该卡属于哪些包。如果没有直接的方法来做到这一点,我应该使用第三个模式函数来存储这些关系吗? 这里有更新更完整的答案:***.com/a/46020968/438970 【参考方案1】:

这就是循环/循环依赖的问题。这就是让它在 nodejs 中工作的方式。有关更多详细信息,请查看 http://exploringjs.com/es6/ch_modules.html#sec_modules-in-javascript 上的“CommonJS 中的循环依赖项”

//------ a.js ------
var b = require('b');
function foo() 
    b.bar();

exports.foo = foo;

//------ b.js ------
var a = require('a'); // (i)
function bar() 
    if (Math.random()) 
        a.foo(); // (ii)
    

exports.bar = bar;

【讨论】:

【参考方案2】:
https://www.npmjs.com/package/mongoose-relationship

##Many-To-Many with Multiple paths

var mongoose = require("mongoose"),
    Schema = mongoose.Schema,
    relationship = require("mongoose-relationship");

var ParentSchema = new Schema(
    children:[ type:Schema.ObjectId, ref:"Child" ]
);
var Parent = mongoose.models("Parent", ParentSchema);

var OtherParentSchema = new Schema(
    children:[ type:Schema.ObjectId, ref:"Child" ]
);
var OtherParent = mongoose.models("OtherParent", OtherParentSchema);

var ChildSchema = new Schema(
    parents: [ type:Schema.ObjectId, ref:"Parent", childPath:"children" ]
    otherParents: [ type:Schema.ObjectId, ref:"OtherParent", childPath:"children" ]
);
ChildSchema.plugin(relationship,  relationshipPathName:['parents', 'otherParents'] );
var Child = mongoose.models("Child", ChildSchema)

var parent = new Parent();
parent.save();
var otherParent = new OtherParent();
otherParent.save();

var child = new Child();
child.parents.push(parent);
child.otherParents.push(otherParent);
child.save() //both parent and otherParent children property will now contain the child's id 
child.remove() 

【讨论】:

【参考方案3】:

我是 node、mongoDB 和 mongoose 的新手,但我认为正确的做法是:

var PackageSchema = new Schema(
    id: ObjectId,
    title:  type: String, required: true ,
    flashcards: [ type : mongoose.Schema.ObjectId, ref : 'Flashcard' ]
);

var FlashcardSchema = new Schema(
    id: ObjectId,
    type:  type: String, default: '' ,
    story:  type: String, default: '' ,
    packages: [ type : mongoose.Schema.ObjectId, ref : 'Package' ]
);

这样,您只存储对象引用而不是嵌入对象。

【讨论】:

很好地找到了这个链接@Ruairi 只想指出这是多对多关系的正确方式。接受的答案是错误的。 @TyMayn 应该设置为模型名称,而不是模式名称。通常 FlashcardSchema 应该生成一个名为 Flashcard 的模型。 mongoosejs.com/docs/models.html @Élodie-petit 请将此作为接受的答案。 引用“填充”文档:It is debatable that we really want two sets of pointers as they may get out of sync. Instead we could skip populating and directly find() the stories we are interested in. 这让我认为其中一个模式应该只有一个 ref 数组。如果您在两种模式中都有 ref 数组,您将希望确保只保存带有包的卡片或带有卡片的包。不过我不是猫鼬方面的专家。【参考方案4】:

您可以使用 Schema.add() 方法来避免前向引用问题。

这个(未经测试的)解决方案将架构放在一个 .js 文件中

models/index.js

var Schema = mongoose.Schema,
    ObjectId = Schema.ObjectId;

// avoid forward referencing
var PackageSchema = new Schema();
var FlashcardSchema = new Schema();

PackageSchema.add(
    id          : ObjectId,
    title       :  type: String, required: true ,
    flashcards  : [ FlashcardSchema ]
);

FlashcardSchema.add(
    id      : ObjectId,
    type        :  type: String, default: '' ,
    story       :  type: String, default: '' ,
    packages    : [ PackageSchema ]
);

// Exports both types
module.exports = 
    Package:   mongoose.model('Package', PackageSchema),
    Flashcard: mongoose.model('Flashcard', FlashcardSchema)
;  

【讨论】:

【参考方案5】:

您的做法是正确的,但问题是您必须在 flashcard-schema.js 中包含 PackageSchema,反之亦然。否则这些文件不知道你在引用什么

var Schema = mongoose.Schema,
    ObjectId = Schema.ObjectId;
    PackageSchema = require('./path/to/package-schema.js')

var FlashcardSchema = new Schema(
    id      : ObjectId,
    type        :  type: String, default: '' ,
    story       :  type: String, default: '' ,
    packages    : [ PackageSchema ]
);

【讨论】:

当每个 require() 加载另一个时,这不会导致无限递归吗? @TonyOHagan 不,Node 处理需求周期,如 nodejs.org/api/all.html#all_cycles 中所述 使用此方法时要小心。当您存储在对象上而不只是其 ID 上时,如果您修改链接到您的“抽认卡”的“包”,它不会在您的“抽认卡”中被修改。 错误答案。下面的答案是正确的。这个答案具有误导性,并且被接受为正确答案。 @gtsouk 回答【参考方案6】:

您认为这太像关系数据存储了。如果这是您想要的,请使用 mysql(或其他 RDBMS)

如果做不到这一点,那么是的,可以使用第三个模式,但不要忘记它仍然只是每个对象的 id(没有连接,请记住),因此您仍然需要检索其中的每个其他项目一个单独的查询。

【讨论】:

我必须将其视为关系 dbms 的唯一地方是这个包卡场景。否则,你是绝对正确的。 根据此答案中的“无连接”评论,请注意 2015 年发布的 MongoDB 现在具有执行左外连接的 $lookup 聚合器 (docs.mongodb.com/manual/reference/operator/aggregation/lookup)。

以上是关于使用 Mongoose 进行多对多映射的主要内容,如果未能解决你的问题,请参考以下文章

首先在实体框架代码中与中间对象进行多对多映射

使用嵌套字段在MongoDB / Mongoose中创建多对多关系?

MongoDB/Mongoose:多对多关系

Hibernate多对多关系映射(建表)

Spring Boot 中的多对多映射问题

Hibernate映射关系:一对一对多和多对多