猫鼬自动增量

Posted

技术标签:

【中文标题】猫鼬自动增量【英文标题】:Mongoose auto increment 【发布时间】:2015-04-06 03:13:49 【问题描述】:

根据this mongodb article,可以自动增加一个字段,我希望使用计数器收集方式。

该示例的问题在于,我没有成千上万的人使用 mongo 控制台在数据库中输入数据。相反,我正在尝试使用猫鼬。

所以我的架构看起来像这样:

var entitySchema = mongoose.Schema(
  testvalue:type:String,default:function getNextSequence() 
        console.log('what is this:',mongoose);//this is mongoose
        var ret = db.counters.findAndModify(
                 query:  _id:'entityId' ,
                 update:  $inc:  seq: 1  ,
                 new: true
               
        );
        return ret.seq;
      
    
);

我在同一个数据库中创建了计数器集合,并添加了一个 _id 为“entityId”的页面。从这里我不确定如何使用猫鼬来更新该页面并获取递增的数字。

计数器没有架构,我希望它保持这种状态,因为这并不是应用程序真正使用的实体。它只能在模式中使用以自动增加字段。

【问题讨论】:

架构默认值不能是异步的,所以这不起作用。如果您在猫鼬plugins page 中搜索“自动增量”,您会找到一些选项。 @JohnnyHK 谢谢你的回复。这些插件在更新事件上工作,这是我宁愿避免的事情,搜索自动增量让我首先找到了 mongodb 文章和似乎基于事件的 mongoose 的 npm 可安装插件。 您能告诉我您使用了哪种方法吗?以及当有多个并发请求时,您的解决方案如何处理?? 问题是sufficiently complex,你应该使用插件。 【参考方案1】:

当您的架构中有唯一字段时,上述答案均无效 因为数据库级别的唯一检查和增量发生在数据库级别验证之前,所以您可能会像上述解决方案一样跳过自动增量中的大量数字 只有在 post save 才能找到数据是否已经保存在 db 上或返回错误

schmea.post('save', function(error, doc, next) 
if (error.name === 'MongoError' && error.code === 11000) 
    next(new Error('email must be unique'));
   else 
    next(error);
  
);

https://***.com/a/41479297/10038067

这就是为什么上述答案都不像 dbs 这样的 sql 中的原子操作自动递增

【讨论】:

您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。【参考方案2】:

test.pre("save",function(next)
    if(this.isNew)
        this.constructor.find().then((result) => 
            console.log(result)
            this.id = result.length + 1;
            next();
          );
    
)

【讨论】:

【参考方案3】:

在通过 put() 为 Schema 的字段分配值时,我在使用 Mongoose Document 时遇到了问题。 count 返回一个 Object 本身,我必须访问它的属性。

我在@Tigran 的回答下玩了,这是我的输出:

// My goal is to auto increment the internalId field
export interface EntityDocument extends mongoose.Document 
    internalId: number


entitySchema.pre<EntityDocument>('save', async function() 
    if(!this.isNew) return;

    const count = await counter.findByIdAndUpdate(
        _id: 'entityId',
        $inc: seq: 1,
        new: true, upsert: true
    );

    // Since count is returning an array
    // I used get() to access its child
    this.internalId = Number(count.get('seq'))
);

版本:猫鼬@5.11.10

【讨论】:

【参考方案4】:

注意!

正如hammerbot 和dan-dascalescu 指出的那样,如果您删除文档,这不起作用

如果您插入 3 个 id 为 123 的文档 - 您删除 2 并插入另一个新文档,它将得到 3 作为已使用的 id!

如果您从未删除过文档,请继续:

我知道这已经有很多答案,但我会分享我的解决方案,它是 IMO 简短易懂的:

// Use pre middleware
entitySchema.pre('save', function (next) 

    // Only increment when the document is new
    if (this.isNew) 
        entityModel.count().then(res => 
            this._id = res; // Increment count
            next();
        );
     else 
        next();
    
);

确保entitySchema._id 具有type:Number。 猫鼬版:5.0.1.

【讨论】:

不要认为它适用于所有情况,但它解决了我的问题。 如果某些文档在某个时候从表格中删除,IMO 这会中断......但无论如何,它也适用于我的用例 正如@Hammerbot 建议的那样,获取下一个序列值的文档计数是非常危险的,对于the same reason the author of the mongoose-sequence package explained。【参考方案5】:

这个问题已经够complicated了,也够pitfalls了,最好依赖一个测试过的猫鼬插件。

在http://plugins.mongoosejs.io/ 的众多“自动增量”插件中,维护和记录最好(而不是分叉)的是mongoose sequence。

【讨论】:

这样可以节省时间。猫鼬序列优于自动增量。谢谢队友【参考方案6】:

我将答案中所有(主观和客观)好的部分结合起来,并提出了以下代码:

const counterSchema = new mongoose.Schema(
    _id: 
        type: String,
        required: true,
    ,
    seq: 
        type: Number,
        default: 0,
    ,
);

// Add a static "increment" method to the Model
// It will recieve the collection name for which to increment and return the counter value
counterSchema.static('increment', async function(counterName) 
    const count = await this.findByIdAndUpdate(
        counterName,
        $inc: seq: 1,
        // new: return the new value
        // upsert: create document if it doesn't exist
        new: true, upsert: true
    );
    return count.seq;
);

const CounterModel = mongoose.model('Counter', counterSchema);


entitySchema.pre('save', async function() 
    // Don't increment if this is NOT a newly created document
    if(!this.isNew) return;

    const testvalue = await CounterModel.increment('entity');
    this.testvalue = testvalue;
);

这种方法的一个好处是所有与计数器相关的逻辑都是独立的。您可以将其存储在单独的文件中,并将其用于导入 CounterModel 的多个模型。

如果您要增加 _id 字段,您应该在您的架构中添加它的定义:

const entitySchema = new mongoose.Schema(
    _id: 
        type: Number,
        alias: 'id',
        required: true,
    ,
    <...>
);

【讨论】:

【参考方案7】:

这是一个建议。

创建一个单独的集合来保存模型集合的最大值

const autoIncrementSchema = new Schema(
    name: String,
    seq:  type: Number, default: 0 
);

const AutoIncrement = mongoose.model('AutoIncrement', autoIncrementSchema);

现在为每个需要的模式添加一个pre-save hook

例如,设集合名称为Test

schema.pre('save', function preSave(next) 
    const doc = this;
    if (doc.isNew) 
         const nextSeq = AutoIncrement.findOneAndUpdate(
              name: 'Test' , 
              $inc:  seq: 1  , 
              new: true, upsert: true 
         );

         nextSeq
             .then(nextValue => doc[autoIncrementableField] = nextValue)
             .then(next);
    
    else next();
 

由于findOneAndUpdateatomic 操作,因此没有两个更新将返回相同的seq 值。因此,您的每次插入都会获得递增的 seq 无论并发插入的数量如何这也可以扩展到更复杂的自增逻辑,自增序列不限于数字类型

这不是经过测试的代码。使用前进行测试,直到我为mongoose 制作插件。

更新发现this插件实现了相关方法。

【讨论】:

该插件不再维护。请参阅this other answer 以获得最佳插件。【参考方案8】:

所以结合多个答案,这就是我最终使用的:

counterModel.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

const counterSchema = new Schema(
  
  _id: type: String, required: true,
  seq:  type: Number, default: 0 
  
);

counterSchema.index( _id: 1, seq: 1 ,  unique: true )

const counterModel = mongoose.model('counter', counterSchema);

const autoIncrementModelID = function (modelName, doc, next) 
  counterModel.findByIdAndUpdate(        // ** Method call begins **
    modelName,                           // The ID to find for in counters model
     $inc:  seq: 1  ,                // The update
     new: true, upsert: true ,         // The options
    function(error, counter)            // The callback
      if(error) return next(error);

      doc.id = counter.seq;
      next();
    
  );                                     // ** Method call ends **


module.exports = autoIncrementModelID;

myModel.js

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

const autoIncrementModelID = require('./counterModel');

const myModel = new Schema(
  id:  type: Number, unique: true, min: 1 ,
  createdAt:  type: Date, default: Date.now ,
  updatedAt:  type: Date ,
  someOtherField:  type: String 
);

myModel.pre('save', function (next) 
  if (!this.isNew) 
    next();
    return;
  

  autoIncrementModelID('activities', this, next);
);

module.exports = mongoose.model('myModel', myModel);

【讨论】:

【参考方案9】:

我不想使用任何插件(一个额外的依赖项,除了我在 server.js 中使用的那个之外,初始化 mongodb 连接等等......)所以我做了一个额外的模块,我可以在任何架构,甚至,我正在考虑何时从数据库中删除文档。

module.exports = async function(model, data, next) 
    // Only applies to new documents, so updating with model.save() method won't update id
    // We search for the biggest id into the documents (will search in the model, not whole db
    // We limit the search to one result, in descendant order.
    if(data.isNew) 
        let total = await model.find().sort(id: -1).limit(1);
        data.id = total.length === 0 ? 1 : Number(total[0].id) + 1;
        next();
    ;
;

以及如何使用它:

const autoincremental = require('../modules/auto-incremental');

Work.pre('save', function(next) 
    autoincremental(model, this, next);
    // Arguments:
    // model: The model const here below
    // this: The schema, the body of the document you wan to save
    // next: next fn to continue
);

const model = mongoose.model('Work', Work);
module.exports = model;

希望对你有帮助。

(如果这是错误的,请告诉我。我对此没有任何问题,但不是专家)

【讨论】:

有趣的解决方案。我唯一担心的是,如果有人从实体集合中删除了一条记录,在您的情况下为Work,使用您的函数生成的自动增量值可能无法满足充当主键的目的(假设它是目的)。 在预存功能中,model未定义前如何使用?【参考方案10】:
var CounterSchema = Schema(
    _id:  type: String, required: true ,
    seq:  type: Number, default: 0 
);
var counter = mongoose.model('counter', CounterSchema);

var entitySchema = mongoose.Schema(
    testvalue:  type: String 
);

entitySchema.pre('save', function(next) 
    if (this.isNew) 
        var doc = this;
        counter.findByIdAndUpdate( _id: 'entityId' ,  $inc:  seq: 1  ,  new: true, upsert: true )
            .then(function(count) 
                doc.testvalue = count.seq;
                next();
            )
            .catch(function(error) 
                throw error;
            );
     else 
        next();
    
);

【讨论】:

你能解释一下它的作用以及它与 3 年前给出的 top answer 有何不同?【参考方案11】:

我同时使用@cluny85 和@edtech。 但是我没有完成这个问题。

counterModel.findByIdAndUpdate(_id: 'aid', $inc: seq: 1 , function(error,counter) 但是在函数“pre('save...)”中,更新计数器的响应在保存文档后完成。 所以我不会将计数器更新为文档。

请再次检查所有答案。谢谢。

对不起。我无法添加评论。因为我是新手。

【讨论】:

【参考方案12】:

投票最多的答案无效。这是修复:

var CounterSchema = new mongoose.Schema(
    _id: type: String, required: true,
    seq:  type: Number, default: 0 
);
var counter = mongoose.model('counter', CounterSchema);

var entitySchema = mongoose.Schema(
    sort: type: String
);

entitySchema.pre('save', function(next) 
    var doc = this;
    counter.findByIdAndUpdateAsync(_id: 'entityId', $inc:  seq: 1 , new: true, upsert: true).then(function(count) 
        console.log("...count: "+JSON.stringify(count));
        doc.sort = count.seq;
        next();
    )
    .catch(function(error) 
        console.error("counter error-> : "+error);
        throw error;
    );
);

options 参数为您提供更新的结果,如果新文档不存在,它会创建一个新文档。 可以查看here官方文档。

如果您需要排序索引,请查看doc

【讨论】:

根据示例:app.post(...) - 我想在哪里使用自动增量 - 我必须在哪里插入这段代码以及它是如何调用的? findByIdAndUpdateAsync 不是 Mongoose 文档中的方法。甚至答案中的链接也指向findByIdAndUpdate【参考方案13】:

即使文档已经有一个 _id 字段(排序等),答案似乎也会增加序列。如果您“保存”以更新现有文档,就会出现这种情况。没有?

如果我是对的,如果 this._id !== 0,你会想调用 next()

猫鼬文档对此并不十分清楚。如果它在内部进行更新类型查询,那么 pre('save' 可能不会被调用。

澄清

似乎确实在更新时调用了“保存”前置方法。

我认为您不想不必要地增加您的序列。它会花费您一次查询并浪费序列号。

【讨论】:

【参考方案14】:

你可以使用mongoose-auto-increment包如下:

var mongoose      = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');

/* connect to your database here */

/* define your CounterSchema here */

autoIncrement.initialize(mongoose.connection);
CounterSchema.plugin(autoIncrement.plugin, 'Counter');
var Counter = mongoose.model('Counter', CounterSchema);

您只需要初始化一次autoIncrement

【讨论】:

该软件包不再维护。截至 2020 年 4 月,维护和记录最好的 mongoose 自动增量包是 mongoose-sequence【参考方案15】:

这是一个如何在 Mongoose 中实现自增字段的示例:

var CounterSchema = Schema(
    _id: type: String, required: true,
    seq:  type: Number, default: 0 
);
var counter = mongoose.model('counter', CounterSchema);

var entitySchema = mongoose.Schema(
    testvalue: type: String
);

entitySchema.pre('save', function(next) 
    var doc = this;
    counter.findByIdAndUpdate(_id: 'entityId', $inc:  seq: 1 , function(error, counter)   
        if(error)
            return next(error);
        doc.testvalue = counter.seq;
        next();
    );
);

【讨论】:

我可以把这个 entitySchema.pre('save', callback);如果我将每个模型放在模型文件夹中的单独文件中? 可以在声明Schema后直接放在同一个文件中 @realisation findByIdAndUpdate 根据mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate 发出一个 原子的 mongodb findAndModify 您能否告诉我,当有多个并发请求时,您的解决方案是否有效?如果是,那怎么办?? 我意识到这有点老了,但是,当您对 entitySchema 进行任何更新(例如更新状态)时,这不会增加计数器,使对 uniqe id 的所有引用都无用吗?也许先检查 isNew。

以上是关于猫鼬自动增量的主要内容,如果未能解决你的问题,请参考以下文章

文档创建猫鼬中的自动增量版本

文档创建猫鼬中的自动增量版本

使用猫鼬生成自动增量字段?

Mongo:将返回的记录限制为切片的增量计数

sql 修改自动增量自动增量

休眠自动增量