【问题标题】:Append inside Schemaless Array追加到无模式数组中
【发布时间】:2017-06-29 12:28:25
【问题描述】:

我是 MongoDB 的新手,到目前为止,我一直在玩它,遇到了一个问题,我在尝试在 Schema-Less Array 中附加多个对象时遇到了困难。到目前为止,我尝试 $push 在数组中附加多个对象,但出现 Mongo 错误

[MongoError: Can't use $push/$pushALL within non-array

我不知道为什么我在使用 $push 和数组时会收到这个错误

架构:

EventTypeSchema = new Schema(){
type: String,
eventID: {
type: Schema.Types.ObjectId,
ref: 'User'
}
} 

PersonSchema = new Schema(){
PersonID: {
type: Schema.Types.ObjectId,
ref: 'User'
}
Invitation: [  ]    //Schema-less
}

在控制器中,我可以访问 EventType 和 Person 模型 控制器:

exports.update = function(req,res){
var event = new EventType();
event.type = 'EVENT';
event.eventID = req.body.eventid;
var query = {'PersonID': req.body.personid};
var update = {$push:{'Invitation': event}};
Person.update(query,update,function(err,user){...})
};

出于调试目的,我尝试为数组提供混合类型架构,但没有让它工作

PersonSchema = new Schema(){
PersonID: {
type: Schema.Types.ObjectId,
ref: 'User'
}
Invitation: [ {
type: Schema.Types.Mixed
} ]    
}       

当我在更新时删除 $push 然后只有整个事件对象进入 Invitation,我创建无模式数组的原因是因为我正在处理不同的类型关于邀请,这里我只是描述了活动邀请,否则我正在处理不同类型的邀请,例如 用户请求邀请会议邀请,所以会有组合不同的objectId的,我认为应该有办法在mongoDB中追加到无模式数组。

编辑:

以下是我想出的。但无法让它工作。

function PortalTypes() {
    Schema.apply(this,arguments);
    this.add({
        object_type: String,
    });
}

util.inherits( PortalTypes, Schema );

var userType = new PortalTypes({
    ID : {
        type: Schema.Types.ObjectId,
        ref : 'User'
    }
});

var eventType = new PortalTypes({
    ID : {
        type: Schema.Types.ObjectId,
        ref : 'events'
    }
});



var user = new userType({ID:'dsaj3232--objectID','object_type':'user'});
user.save();

var event = new eventType({ID:'dasddehiqe98--objectID','object_type':'event'});
event.save();

Networks.Invitation.push(user,event);

我该怎么做?

【问题讨论】:

  • 嗯,也许我没听懂,但是PersonIDPersonSchema 里面在哪里?
  • 对不起,我没有提到,我会更新问题

标签: javascript node.js mongodb mongoose mongodb-query


【解决方案1】:

尽管您的架构,顶部的错误意味着集合中有一个匹配的文档没有将此字段设置为数组,但它以另一种类型存在。可能只是一个字符串或对象。

这里有一个小小的、人为的示例清单来演示:

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"
        }
    ]
}

如您所见,PersonBoss 的保存方式不同,特别是 _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 运算符与数组内容一起使用,所以它应该在一个地方。但是您仍然可以像所示那样解析类型。

如果您希望使用单独的集合,那么您将手动完成所有操作并存储模型名称并查询其他集合以获取引用。

【讨论】:

  • 感谢您的关注!我试图使用你的方法,但得到了这个错误---tasks[index].apply(null, arguments); ^ TypeError:无法调用未定义的方法“应用”我做了console.log,控制没有进入任何瀑布函数
  • @davidwarne 每个列表和输出都按原样运行,并直接从我的控制台复制和粘贴。我怀疑您正在更改自己的代码并且那里有问题。鉴别器路径似乎是你需要去的地方,另一个清单只是为了解释之前给出的错误。不确定您要更改什么代码,但如果您遵循给定的模型,那么您应该没问题。
  • 在您的示例中,PersonSchema 和 BossSchema 是相同 BaseSchema 的对象,在 Boss 模式中有一个额外的参数。我的要求是 PersonSchema 和 BossSchema 是两种完全不同的类型。我想让 people 数组能够保存来自不同文档的 ObjectID,比如文档 A 和文档 B,然后能够调用 populate 来填充其中的信息。我不认为你的方法适用于此。
  • @davidwarne 只是一个_id 字段也可以用作BaseSchema,所以一个空白Schema 与省略代码的this.add( 部分基本相同。都一样。
  • 在包含我从您的回答中学到的内容后,用我想要的内容编辑了问题。如果我错了,请纠正我
猜你喜欢
  • 2015-08-03
  • 1970-01-01
  • 1970-01-01
  • 2016-12-25
  • 2020-09-26
  • 2017-10-22
  • 2015-02-25
  • 1970-01-01
  • 2016-08-23
相关资源
最近更新 更多