附加在Schemaless Array中

时间:2014-12-29 18:13:37

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

我是 MongoDB 的新手,到目前为止一直在玩它,面对一个问题,这里我很难在Schema-Less Array中附加多个对象。到目前为止我尝试 $ push 在数组中附加多个对象但得到 Mongo错误

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

当使用$ push with array

时,我不知道为什么会出现此错误

架构:

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
}

在Controller中,我可以访问EventType和Person Model 控制器:

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 时,只有整个事件对象进入邀请,我创建无架构数组的原因是因为我正在处理不同类型邀请,这里我刚刚描述了活动邀请,否则我正在处理不同类型的邀请,如用户邀请请求会议邀请,所以会有组合不同的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);

我该怎么办?

1 个答案:

答案 0 :(得分: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模式定义并直接进入数据库。所以可以这样做,也可能只是已经有一个文档,或者在不同的模式定义到位时创建。

这是第一个问题。


你似乎要问的其余部分是"多态"数组中的关联类型,以及您不希望"嵌入"整个创建的对象在数组中,但只是对它的引用。

Mongoose有"discriminators"允许这种事情,允许对象的不同模型类型存储在同一个集合中,但是解析为他们自己的对象和模式&#34;键入&#34;。< / p>

按照当前的文档示例,下面是一个示例列表:

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对象上时,只有&#34;引用ID&#34;值存储在数组中。对你可能想要的东西有争议,但这是&#34;引用&#34;之间的区别。和&#34;嵌入&#34;模式模型。但是,您可以看到调用.populate()方法时,在从引用的集合中读取对象时,对象将恢复为完整形式。


因此,请检查您的集合中是否存在与您的架构定义不同的现有文档,并考虑所示的方法来表示&#34;多态&#34;不同类型的协会&#34;对象。

请注意,这种解决方案仅在&#34;引用&#34;模式设计,也可能有它的缺点。如果您希望将对象存储为&#34; embedded&#34;在单个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();
  }
);

这应该解释一些事情和什么&#34;鉴别器&#34;是。一切都存储在&#34; one&#34;绑定到基本模型的集合。其他所有内容都是使用该基地的.discriminator()定义的。 &#34;名称&#34; &#34;班级模型&#34;或者&#34;鉴别器&#34;存储在对象上。但请注意,它仅存储在集合中,而不是存储在它们被引用的位置,因为它只存储_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

因此,所有&#34;门户网站只有一个集合&#34;类型,但有一些魔术,如图所示。 &#34;其他&#34;集合仅将_id值存储在&#34;门户网站&#34;的数组中。这就是mongoose引用的方式,其中&#34;模型&#34;并且附加的模式不存储在数据中,而是作为代码定义的一部分。

&#34;鉴别器&#34;零件存储此&#34;型号名称&#34;在场上,所以它可以被解析为正确的类型,但它仍然在同一个集合中,并且User模型魔法的一部分被证明了。

为什么呢?这是.populate()的工作原理。在引擎盖下, $in 运算符与数组内容一起使用,因此预计它们都在一个位置。但您仍然可以解析所显示的类型。

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