MongoDB mongoose 子文档创建了两次

Posted

技术标签:

【中文标题】MongoDB mongoose 子文档创建了两次【英文标题】:MongoDB mongoose subdocuments created twice 【发布时间】:2017-03-15 13:07:12 【问题描述】:

我正在使用一个简单的表格,可用于将文章注册到网站。

后端如下所示:

// Post new article
app.post("/articles", function(req, res)
   var newArticle = ;
   newArticle.title         = req.body.title;
   newArticle.description   = req.body.description;
   var date                 = req.body.date;
   var split                = date.split("/");
   newArticle.date          = split[1]+'/'+split[0]+'/'+split[2];
   newArticle.link          = req.body.link;
   newArticle.body          = req.body.body;
   var platforms = req.body.platforms;
   console.log(platforms);
  Article.create(newArticle, function(err, createdArticle)
      if(err)
          console.log(err.message);
       else 
           var counter=0;
            platforms.forEach(function(platform)

               var platformed=mongoose.mongo.ObjectID(platform);
               Platform.findById(platformed, function(err, foundPlatform)
                  if(err)
                      console.log(err);
                   else 
                      counter++;
                        foundPlatform.articles.push(createdArticle);
                        foundPlatform.save();
                        createdArticle.platforms.push(foundPlatform);
                        createdArticle.save();
                        if(counter==platforms.length)
                            res.redirect('articles/' + createdArticle._id);
                        
                    
                );
            );

      


  );

);

platforms 字段作为字符串数组传递到后端,一个字符串是一个 objectID。当平台仅包含 1 个字符串(即要链接到的 1 个平台)时,一切正常。当平台包含多个字符串时。创建的文章具有每个平台的副本。或者有时只是某些平台的副本

有什么想法吗?

更新 1: 文章架构: var mongoose = require("mongoose");

var articleSchema = new mongoose.Schema(
    title        :   String,
    description  :   String, 
    link         :   String,
    date         :   String,
    body         :   String,
    platforms    :   [
      
         type: mongoose.Schema.Types.ObjectId,
         ref: "Platform"
      
   ] 
)

module.exports = mongoose.model("Article", articleSchema);

平台架构:

var mongoose = require("mongoose");

var platformSchema = new mongoose.Schema(
    name        :   String,
    category            :   String,
    contacts          :   [
      
         type: mongoose.Schema.Types.ObjectId,
         ref: "Contact"
      
   ],
   website              :   String,
   country              :   String,
   contactInformation   :   String,
   businessModelNotes   :   String,
   source               :   String,
   generalNotes         :   String,
   projects             :   [
      
         type: mongoose.Schema.Types.ObjectId,
         ref: "Project"
      
   ],
   articles             :   [
      
         type: mongoose.Schema.Types.ObjectId,
         ref: "Article"
      
   ],
   privacy              :   String,
   comments             :   [
      
         type: mongoose.Schema.Types.ObjectId,
         ref: "Comment"
      
   ]


);



module.exports = mongoose.model("Platform", platformSchema);

【问题讨论】:

您能否edit 向我们展示ArticlePlatform 模型的架构定义?我可以看到你在一个循环中进行异步调用,这可能解释了为什么你会得到重复。 更新了! @chridam 【参考方案1】:

移动你的保存功能

if(counter==platforms.length)
     createdArticle.save(function(err, savedObject)
        if(err || !savedObject) console.log(err || "not saved");
        else 
          res.redirect('articles/' + savedObject._id.toString());
        
     );

============= 编辑

这是因为您只需调用一次 article.save,而不是在每个循环中。此外,您使用 save() 作为同步函数,但它是异步的。

我认为你应该直接使用更新功能:

 else 
  var counter=0;
  // map plateform array id with ObjectID
  var idarray = platforms.map(function(e)return mongoose.mongo.ObjectID(e););

  // update all plateform with article id
  Platform.update(_id:$in: idarray, $push:articles: createdArticle, multi:true, upsert:false, function(err, raw)
    if(err)
    
       // error case
       return res.status(403).json();
    
    // retrieve plateform
    Platform.find(_id:$in: idarray, function(err, results)
      if(err || !results)
      
          // error case 
          return res.status(403).json();
      
      Article.update(_id: createdArticle._id.toString(), $push:platforms:$each: results, multi:false, upsert:false, function(err, saved)
          if(err || !saved)
          
             // error
              return res.status(403).json();
          
          res.redirect('articles/' + savedObject._id.toString());
     );
   );
 );

但是存储完整的对象是个坏主意,为什么不只存储 id 呢??

【讨论】:

非常感谢。 2个快速问题。我是否必须为其他保存功能做同样的事情。这里出了什么问题 是的,因为你在循环中使用了异步函数。见编辑 所以我应该用这个替换我 else 中的所有内容?【参考方案2】:

您尝试中的 forEach 循环在下一次迭代之前无法识别 findById() 异步方法的回调完成。您需要使用任何async 库方法async.eachasync.whilstasync.until,它们等效于for 循环,并且将等到调用异步回调,然后再进行下一次迭代(换句话说,一个将产生的 for 循环)。

例如:

var platform_docs = [];
async.each(platforms, function(id, callback) 
    Platform.findById(id, function(err, platform) 
        if (platform) 
            platform_docs.push(platform);
        callback(err);
    );
, function(err) 
   // code to run on completion or err
   console.log(platform_docs);
);

对于整个操作,您可以使用 async.waterfall() 方法,该方法允许每个函数将其结果传递给下一个函数。

方法中的第一个函数创建新文章。

第二个函数使用async.each() 实用函数遍历平台列表,为每个id 执行异步任务以使用findByIdAndUpdate() 更新平台,当它们都完成时将对象变量中的更新查询结果返回给下一个函数。

最后一个函数将使用上一个管道中的平台 ID 更新新创建的文章。

类似于以下示例:

var newArticle = ,
    platforms            = req.body.platforms,
    date                 = req.body.date,
    split                = date.split("/");

newArticle.title         = req.body.title;
newArticle.description   = req.body.description;
newArticle.date          = split[2]+'/'+split[0]+'/'+split[2];
newArticle.link          = req.body.link;
newArticle.body          = req.body.body;
console.log(platforms);

async.waterfall([

    // Create the article
    function(callback) 
        var article = new Article(newArticle);
        article.save(function(err, article)
            if (err) return callback(err);                  
            callback(null, article);
        );
    ,

    // Query and update the platforms 
    function(articleData, callback) 
        var platform_ids = [];
        async.each(platforms, function(id, callback) 
            Platform.findByIdAndUpdate(id, 
                 "$push":  "articles": articleData._id  ,
                 "new": true ,
                function(err, platform) 
                    if (platform) 
                        platform_ids.push(platform._id);
                    callback(err);
                
            );
        , function(err) 
            // code to run on completion or err
            if (err) return callback(err);                  
            console.log(platform_ids);
            callback(null, 
                "article": articleData,
                "platform_ids": platform_ids
            );
        );         
    ,

    // Update the article
    function(obj, callback) 
        var article = obj.article;
        obj.platform_ids.forEach(function(id) article.platforms.push(id); );
        article.save(function(err, article)
            if (err) return callback(err);                  
            callback(null, article);
        );
       

], function(err, result)  
/*
    This function gets called after the above tasks 
    have called their "task callbacks"
*/
    if (err) return next(err);
    console.log(result);
    res.redirect('articles/' + result._id);
);

【讨论】:

如果您正在编辑链接文章@chridam的平台,您将如何应用此功能 您能为此创建一个新问题吗? 我想知道您是否介意查看我发布的关于同一问题的新问题。 question2@chridam

以上是关于MongoDB mongoose 子文档创建了两次的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB:如何使用 Mongoose 添加或更新子文档?

MongoDB/Mongoose - 与 geoNear 和子文档的聚合

基于多个子文档的MongoDB/Mongoose查询

使用 mongoose 从 Mongodb 中删除子文档

如何在 mongoose/mongodb 查询子文档中使用 mapreduce?

如何在 mongoose/mongodb 查询子文档中使用 mapreduce?