尽管您的架构,顶部的错误意味着集合中有一个匹配的文档没有将此字段设置为数组,但它以另一种类型存在。可能只是一个字符串或对象。
这里有一个小小的、人为的示例清单来演示:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
var personSchema = new Schema({
invitation: []
});
var Person = mongoose.model( 'Person', personSchema );
mongoose.connect('mongodb://localhost/test');
async.waterfall(
[
function(callback) {
Person.remove({},function(err,num) {
callback(err);
});
},
function(callback) {
console.log( "Creating" );
var person = new Person();
person.save(function(err,person) {
if (err) callback(err);
console.log(person);
callback(err,person);
});
},
function(person,callback) {
console.log( "Updating" );
Person.findOneAndUpdate(
{ "_id": person._id },
{ "$push": { "invitation": "something" } },
function(err,doc) {
if (err) callback(err);
console.log(doc);
callback(err);
}
);
},
function(callback) {
console.log( "Upserting" );
Person.findOneAndUpdate(
{ "name": "bob" },
{ "$set": { "invitation": {} } },
{ "upsert": true },
function(err,doc) {
if(err) callback(err);
console.log(doc);
callback(err,doc);
}
);
},
function(bob,callback) {
console.log( "Failing" );
Person.findOneAndUpdate(
{ "name": "bob" },
{ "$push": { "invitation": "else" } },
function(err,doc) {
if (err) callback(err);
console.log(doc);
callback(err);
}
);
}
],
function(err) {
if (err) throw err;
console.log( "Done" );
mongoose.disconnect();
}
);
结果应该是这样的:
Creating
{ __v: 0, _id: 54a18afb345b4efc02f21020, invitation: [] }
Updating
{ _id: 54a18afb345b4efc02f21020,
__v: 0,
invitation: [ 'something' ] }
Upserting
{ _id: 54a18afb9997ca0c4a7eb722,
name: 'bob',
__v: 0,
invitation: [ {} ] }
Failing
/home/neillunn/scratch/persons/node_modules/mongoose/lib/utils.js:413
throw err;
^
MongoError: exception: The field 'invitation' must be an array but is of type Object
in document {_id: ObjectId('54a18afb9997ca0c4a7eb722')}
错误消息有点不同,因为它们在 MongoDB 2.6 及更高版本(此错误字符串的来源)中进行了一些改进,以便更准确地了解实际问题。因此,在现代版本中,您会被告知具体出了什么问题。
尽管有模式,但像 .update() 这样的方法(为方便起见,我使用了.findOneAndUpdate())在某种程度上绕过了猫鼬模式定义并直接进入数据库。因此,可以这样做,也有可能您已经有了一个文档,或者在不同的架构定义到位时创建了一个文档。
这是这里的第一个问题。
您似乎要求的其余内容是数组中的“多态”关联类型,并且您不希望将整个创建的对象“嵌入”到数组中,而只是对其的引用。
Mongoose 有 "discriminators" 允许这种事情,允许对象的不同模型类型存储在同一个集合中,但解析为它们自己的对象和模式“类型”。
按照当前的文档示例,下面是一个可能看起来像的示例列表:
var util = require('util'),
async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
function logger(label,content) {
console.log(
"%s:\n%s\n", label, JSON.stringify( content, undefined, 4 ) );
}
function BaseSchema() {
Schema.apply(this,arguments);
this.add({
name: String,
createdAt: { type: Date, default: Date.now }
});
}
util.inherits( BaseSchema, Schema );
var personSchema = new BaseSchema(),
bossSchema = new BaseSchema({ department: String });
var companySchema = new Schema({
people: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
});
var Person = mongoose.model( 'Person', personSchema ),
Boss = Person.discriminator( 'Boss', bossSchema ),
Company = mongoose.model( 'Company', companySchema );
mongoose.connect('mongodb://localhost/test');
async.waterfall(
[
function(callback) {
Company.remove({},function(err,num) {
callback(err);
});
},
function(callback) {
Person.remove({},function(err,num) {
callback(err);
});
},
function(callback) {
var person = new Person({ name: "Bob" });
person.save(function(err,person) {
logger("Person", person);
callback(err,person);
});
},
function(person,callback) {
var boss = new Boss({ name: "Ted", department: "Accounts" });
boss.save(function(err,boss) {
logger("Boss", boss);
callback(err,person,boss);
});
},
function(person,boss,callback) {
var company = new Company();
company.people.push(person,boss);
company.save(function(err,company) {
logger("Stored",company);
callback(err,company);
});
},
function(company,callback) {
Company.findById(company.id)
.populate('people')
.exec(function(err,company) {
logger("Polulated",company);
callback(err);
});
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
这将产生如下输出:
Person:
{
"__v": 0,
"name": "Bob",
"createdAt": "2014-12-29T17:53:22.418Z",
"_id": "54a1951210a7a1b603161119"
}
Boss:
{
"__v": 0,
"name": "Ted",
"department": "Accounts",
"__t": "Boss",
"createdAt": "2014-12-29T17:53:22.439Z",
"_id": "54a1951210a7a1b60316111a"
}
Stored:
{
"__v": 0,
"_id": "54a1951210a7a1b60316111b",
"people": [
"54a1951210a7a1b603161119",
"54a1951210a7a1b60316111a"
]
}
Polulated:
{
"_id": "54a1951210a7a1b60316111b",
"__v": 0,
"people": [
{
"_id": "54a1951210a7a1b603161119",
"name": "Bob",
"__v": 0,
"createdAt": "2014-12-29T17:53:22.418Z"
},
{
"_id": "54a1951210a7a1b60316111a",
"name": "Ted",
"department": "Accounts",
"__v": 0,
"__t": "Boss",
"createdAt": "2014-12-29T17:53:22.439Z"
}
]
}
如您所见,Person 和 Boss 的保存方式不同,特别是 _t 属性以及不同对象的其他定义属性。然而,两者实际上都存储在同一个“人”集合中,并且可以这样查询。
当将这些存储在 Company 对象上时,只有“引用 id”值存储在数组中。您可能想要什么值得商榷,但这是“引用”和“嵌入”模式模型之间的区别。但是,您可以看到,当调用 .populate() 方法时,对象会在从引用的集合中读取时恢复为其完整形式。
因此,请检查您的集合中是否存在与您的架构定义不同的现有文档,并考虑用所示方法表示不同“类型”对象的“多态”关联。
请注意,这种解决方案仅在“引用”模式设计下受支持,这也可能有它的缺点。如果您希望将对象存储为“嵌入”在单个 Company 集合中(例如),那么您不会获得由 mongoose 自动完成的具有不同模式类型的对象解析类型。解析不同类型的对象必须在您的代码中手动完成,或者提供插件或者您执行此操作。
更多
针对所有目的,因为基于标准文档示例的某些内容似乎存在一些混淆,这里有一个评论较多的清单:
var util = require('util'),
async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
// Utility
function logger(label,content) {
console.log(
"%s:\n%s\n", label,
util.inspect( content, false, 8, false ) );
}
/*
* Schemas:
*
* you can use a base schema for common fields or just a plain
* definition
*/
var portalSchema = new Schema(),
userSchema = new Schema({
"name": String,
"age": Number
}),
eventSchema = new Schema({
"place": String,
"eventDate": { type: Date, default: Date.now }
});
/*
* Models
*
* there is only one "model" defined and therefore one collection only
* as everything is comes from a stored __v field with the "Model" name
* defined in the discriminator
*/
var Portal = mongoose.model( 'Portal', portalSchema ),
User = Portal.discriminator( 'User', userSchema ),
Event = Portal.discriminator( 'Event', eventSchema );
/*
* Then there is the thing that is going to consume the references to the
* 'Portal' model. The array here references the "base" model.
*/
var otherSchema = new Schema({
"afield": String,
"portals": [{ type: Schema.Types.ObjectId, ref: "Portal" }]
});
var Other = mongoose.model( 'Other', otherSchema );
/*
* Meat:
*
* Let's start doing things
*/
mongoose.connect('mongodb://localhost/test');
// Just because we're passing around objects without globals or other scoping
async.waterfall(
[
// Start fresh by removing all objects in the collections
function(callback) {
Other.remove({},function(err,num) {
callback(err);
});
},
function(callback) {
Portal.remove({},function(err,num) {
callback(err);
});
},
// Create some portal things
function(callback) {
var eventObj = new Event({ "place": "here" });
eventObj.save(function(err,eventObj) {
logger("Event", eventObj);
callback(err,eventObj);
});
},
function(eventObj,callback) {
var userObj = new User({ "name": "bob" });
userObj.save(function(err,userObj) {
logger("User", userObj);
callback(err,eventObj,userObj);
});
},
// Store the references in the array for the Other model
function(eventObj,userObj,callback) {
var other = new Other({
"afield": "something"
});
other.portals.push(eventObj,userObj);
other.save(function(err,other) {
logger("Other Stored",other);
callback(err,other);
});
},
// See how it's all really stored
function(other,callback) {
Portal.find({},function(err,portals) {
logger("Portals",portals);
callback(err,other);
});
},
// But watch the magic here
function(other,callback) {
User.find({},function(err,portals) {
logger("Just Users!",portals);
callback(err,other);
});
},
// And constructed as one object by populate
function(other,callback) {
Other.findById(other.id)
.populate('portals')
.exec(function(err,other) {
logger("Other populated",other);
console.log("%s: %s",
"1st Element", other.portals[0].constructor.modelName );
console.log("%s: %s",
"2nd Element", other.portals[1].constructor.modelName );
callback(err);
});
}
],
function(err) {
// It's just a script, so clean up
if (err) throw err;
mongoose.disconnect();
}
);
这应该可以解释一些事情以及“鉴别器”是什么。一切都存储在绑定到基本模型的“一个”集合中。其他所有内容都是使用该基础的.discriminator() 定义的。 “类模型”或“鉴别器”的“名称”存储在对象上。但请注意,它仅存储在集合中,而不是在引用它们的位置,因为它仅存储 _id 值。仔细看输出:
Event:
{ __v: 0,
place: 'here',
__t: 'Event',
_id: 54a253ec456b169310d131f9,
eventDate: Tue Dec 30 2014 18:27:40 GMT+1100 (AEDT) }
User:
{ __v: 0,
name: 'bob',
__t: 'User',
_id: 54a253ec456b169310d131fa }
Other Stored:
{ __v: 0,
afield: 'something',
_id: 54a253ec456b169310d131fb,
portals: [ 54a253ec456b169310d131f9, 54a253ec456b169310d131fa ] }
Portals:
[ { _id: 54a253ec456b169310d131f9,
place: 'here',
__v: 0,
__t: 'Event',
eventDate: Tue Dec 30 2014 18:27:40 GMT+1100 (AEDT) },
{ _id: 54a253ec456b169310d131fa,
name: 'bob',
__v: 0,
__t: 'User' } ]
Just Users!:
[ { _id: 54a253ec456b169310d131fa,
name: 'bob',
__v: 0,
__t: 'User' } ]
Other populated:
{ _id: 54a253ec456b169310d131fb,
afield: 'something',
__v: 0,
portals:
[ { _id: 54a253ec456b169310d131f9,
place: 'here',
__v: 0,
__t: 'Event',
eventDate: Tue Dec 30 2014 18:27:40 GMT+1100 (AEDT) },
{ _id: 54a253ec456b169310d131fa,
name: 'bob',
__v: 0,
__t: 'User' } ] }
1st Element: Event
2nd Element: User
因此,所有“门户”类型只有一个集合,但如图所示,那里有一些魔力。 “其他”集合仅将 _id 值存储在其“门户”数组中。这就是猫鼬引用的工作方式,其中“模型”和附加模式不存储在数据中,而是作为代码定义的一部分。
“鉴别器”部分将这个“模型名称”存储在字段中,以便可以将其解析为正确的类型,但它们仍然都在同一个集合中,并展示了 User 模型魔法的一部分。
为什么?这就是.populate() 的工作方式。在引擎盖下,$in 运算符与数组内容一起使用,所以它应该在一个地方。但是您仍然可以像所示那样解析类型。
如果您希望使用单独的集合,那么您将手动完成所有操作并存储模型名称并查询其他集合以获取引用。