【问题标题】:Automatically Populate Subdocuments After Saving保存后自动填充子文档
【发布时间】:2017-12-16 01:57:00
【问题描述】:

我希望在保存特定模型后自动填充子文档。我真正想要的是如下所示:

MyModel.post('save', function(doc, next) {
    doc.populate('path').then(next);
});

但是,上述方法不起作用,因为

post 中间件不直接接收流量控制,例如没有 next 或 done 回调传递给它。 post 钩子是为这些方法注册传统事件监听器的一种方式。

当然,还有“异步 Post Hooks”,但它们仍然不接收控制流,因此无法保证在我需要时会填充子文档。

为什么不直接使用嵌入文档呢?对于这种情况,子文档与父文档相同(例如,我正在使用类似 Composite 模式的东西),这将导致我不确定如何解决(或者我什至可以解决它)的循环依赖.对于其他情况,我可能希望能够在不通过父文档的情况下访问子文档。

我考虑的另一种方法是:

const nativeSave = /* ? */;

MyModel.methods.save = function save() {
    return nativeSave.call(this).then(function(savedDoc) {
        return savedDoc.populate('path');
    });
};

但是,这样做有两个问题。首先,这似乎是一个迂回的解决方案。如果有一种更原生的方法不违背 mongoose 的实现,我会更喜欢。其次,我不知道我会将nativeSave 设置为什么(/* ? */ 很明显)。

那么,对于获得这种行为有什么建议?如果我的第二个解决方案对于当前版本的猫鼬来说是最好的,我应该将nativeSave 设置为什么?我已经考虑过嵌入式文档,所以请不要建议使用它们,除非您提供有关解决循环依赖的建议。即便如此,我也想要我提到的其他用例的解决方案。

正如我在评论中所解释的,这与在保存后手动填充子文档不同,因为其他帖子的要求。我希望这能自动发生,以避免泄露我的实现细节(例如,使用 ObjectIds 而不是真实文档)。

【问题讨论】:

  • Mongoose populate after save 的可能重复项。如果您问如何确保这种情况总是发生,我不确定这是一个真实的用例。
  • 那篇文章是关于在保存后手动填充单个文档,我的问题是让这在幕后自动发生。我展示了完成前者所需的知识,并在问题中提供了我的用例。基本上,我不想将我的实现细节泄露给使用我的模型的东西,这似乎是合理的。
  • 我想你可以对模型的保存方法进行猴子补丁。这可能是个坏主意(你不知道你是否总是想在每次保存时都进行填充)。即使您走这条路,或者您使用了静态或模式方法,该线程也会向您展示所有方法。

标签: mongoose


【解决方案1】:

我要说的是,即使可以对内置的 mongoose 方法(如“save”或“find”)进行mongopatch,也可能会很糟糕主意。除了并非每次调用 save 方法都需要产生额外的 populate 调用的开销之外,您当然不希望通过下拉到底层 mongo 驱动程序来更改函数的工作方式(您失去了验证,生命循环方法,如果您想使用 mongoose 文档,则必须重新查询数据库)。

您冒着破坏任何依赖于“save”以某种方式工作的代码的巨大风险。各种各样的插件都不在讨论范围内,你可能会让任何追随你的开发人员大吃一惊。我不允许在我负责的代码库中使用它。

所以你只需要创建一个静态或模式方法。在该方法中,您将调用保存,然后是填充。像这样的:

MyModel.methods.saveAndPopulate = function(doc) {
  return doc.save().then(doc => doc.populate('foo').execPopulate())
}

这几乎是这里建议的最新方法:Mongoose populate after save。这就是我投票结束您的问题的原因。

【讨论】:

    【解决方案2】:

    这会有所帮助

    http://frontendcollisionblog.com/mongodb/2016/01/24/mongoose-populate.html

     var bandSchema = new mongoose.Schema({
      name: String,
      lead: { type: mongoose.Schema.Types.ObjectId, ref: 'person' }
    });
    
    var autoPopulateLead = function(next) {
      this.populate('lead');
      next();
    };
    
    bandSchema.
      pre('findOne', autoPopulateLead).
      pre('find', autoPopulateLead);
    
    var Band = mongoose.model('band', bandSchema);
    

    【讨论】:

      【解决方案3】:

      意识形态

      我实际上并不认为这种“猴子修补”甚至是不好的做法。当您想到它时,模型(例如上面的MyModel 示例)继承自猫鼬的Model 类。因此,我认为它更多地扩展了基本 save() 行为。如果您覆盖 mongoose 方法和/或使用保留关键字,这就是维护者拒绝抛出错误的原因之一。

      我更喜欢在可能的情况下封装数据,对外部世界保密。在这种情况下,我不希望任何外部代码知道我在数据库中使用ObjectIds。相反,我希望它认为我总是直接使用子文档。如果我泄露了我的实现细节(使用ObjectIds),我的代码将变得过于紧密耦合,使维护和更新成为一场噩梦。有很多资源可以更深入地了解封装及其好处。

      此外,我相信 SoC 和模块化代码。我觉得让你的代码过于依赖猫鼬对save()(或任何其他方法)的实现会使你的代码过于脆弱,Uncle Bob 似乎同意。如果 mongoose 死了,我们切换到另一个 DBMS,或者save() 的实现发生了重大变化,如果我依赖于 Mongoose 的实现细节,我们就完蛋了。我喜欢我的代码尽可能与 Mongoose 和其他我使用的库分开。

      如果这是一种普通的多线程 OO 语言,我们可能甚至不会进行这种讨论。 save() 方法将被继承并阻塞,直到子文档被填充。

      免责声明

      扩展 Mongoose 方法的默认行为可能很危险,您应该格外小心。确保您了解自己在做什么,考虑过后果和替代方案,并进行了广泛的测试。您可能还想与您的团队讨论这种方法。

      如果您有疑问和/或您认为 Robert Moskal 的解决方案足以满足您的需求,我建议您使用该方法。事实上,我在我的许多模型中都使用了类似的方法。

      注意事项

      修改save() 将影响save() 的每个实例。如果不希望这样做,请考虑 Robert Moskal 的方法。就我而言,我总是希望在保存后自动填充子文档。

      这种方法也会对性能产生影响。如果您有许多深度嵌套的文档,则此方法可能不适合您。也许 Robert Moskal 的解决方案或在您的模型上定义返回 Promises 的方法(或使用 async/await)会更合适。就我而言,性能影响可以忽略不计且可以接受。

      此外,意识形态部分讨论的许多概念都非常重要并且适用于这种情况。这也表明这种方法是合适的。

      解决方案

      如上所述,我认为MyModel 继承自猫鼬的Model 类。所以,我可以扩展MyModel 的save() 方法的行为,就像我扩展任何子类的继承方法的行为一样:

      MyModel.methods.save = function save(options, callback) {
          return mongoose.Model.prototype.save.call(this, options).then(function(savedDoc) {
              return savedDoc.populate('path');
          }).then(
              function(populatedDoc) {
                  if(callback) {
                      callback(null, populatedDoc);
                  }
      
                  return populatedDoc;
              },
              function(error) {
                  if(callback) {
                      return callback(error);
                  }
      
                  throw error;
              }
          });
      };
      

      不幸的是,JS 并没有真正的super 概念,所以就像在 JS 中扩展的任何其他继承方法一样,我必须知道超类才能访问该方法。除此之外,一切都非常简单明了。

      【讨论】:

      • 我通常不喜欢将自己的答案标记为正确,但除非出现另一个更好的答案,否则我会将其标记为正确。
      【解决方案4】:
      // extract only key of body
      // const updates = Object.keys(req.body);
      let model = Model.findById({_id}
      // map automatically attributes
      _.extend(congregation, req.body); // using lodash
      
      // OR
      // updates.forEach(update => {
      //   model[update] = req.body[update];
      // });
      
      await model.save().then(model => // without curly braces
            model
              .populate('a')
              .populate('b')
              .populate('c')
              .populate('d')
              .execPopulate(),
          );
      
      res.status(201).send(model);
      

      【讨论】:

        猜你喜欢
        • 2014-09-06
        • 1970-01-01
        • 2019-07-04
        • 2014-08-16
        • 2017-01-27
        • 1970-01-01
        • 2015-10-10
        • 2023-03-14
        • 2017-04-06
        相关资源
        最近更新 更多