猫鼬自动增量
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 为
1
、2
和3
的文档 - 您删除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 themongoose-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();
由于findOneAndUpdate
是atomic
操作,因此没有两个更新将返回相同的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后直接放在同一个文件中 @realisationfindByIdAndUpdate
根据mongoosejs.com/docs/api.html#model_Model.findByIdAndUpdate 发出一个 是原子的 mongodb findAndModify
。
您能否告诉我,当有多个并发请求时,您的解决方案是否有效?如果是,那怎么办??
我意识到这有点老了,但是,当您对 entitySchema 进行任何更新(例如更新状态)时,这不会增加计数器,使对 uniqe id 的所有引用都无用吗?也许先检查 isNew。以上是关于猫鼬自动增量的主要内容,如果未能解决你的问题,请参考以下文章