对mongoose子文档数组复杂的补充

我的数据模型有帐户,帐户有一些信用交易。 我已经将这些交易devise为子文档:

var TransactionSchema = new Schema({ amount: Number, added: Date }), AccountSchema = new Schema({ owner: ObjectId, balance: Number, transactions: [TransactionSchema] }); 

Transaction被添加到Account ,应该发生以下情况:

  • transactions有新的推动
  • transactions按datesorting(稍后显示)
  • balance设置为所有transactions的总和

我已经把它放在一个Schema.methods函数中,在保存之前用JavaScript来完成。 但是,我不确定多次插入是否安全。

如何在Mongoose中更好地解决使用primefaces或某种事务更新? 在SQL中,我只是做一个事务,但我不能在MongoDB中,所以如何确保transactionsbalance总是正确的?

您可以通过一次update调用来完成所有这些操作,这些调用将所有这三种操作(这是将原始更新组合在一起的唯一方法)结合在一起。 您不会在更新期间对交易进行总结,而是根据更改的金额更新balance

 var transaction = { amount: 500, added: new Date() }; Account.update({owner: owner}, { // Adjust the balance by the amount in the transaction. $inc: {balance: transaction.amount}, // Add the transaction to transactions while sorting by added. $push: {transactions: { $each: [transaction], $sort: {added: 1} }} }, callback); 

请注意,这确实使用了$sort推导的$sort修饰符,它是在2.4中添加的,并且在2.6中进行了更新,因此您需要使用最近的构build。

这个答案的相同之处很多,只是殴打我,但我确实有一个更长的解释,所以需要一段时间。

所以非常多一个调整同样的事情。 在你的天平上使用$inc操作符进行交易是你想要的,而不是重新计算,所以基本上这个代码块:

  bucket.find({ "account": accId, "owner": owner, "day": day }).upsert().updateOne( { "$inc": { "balance": amount }, "$push": { "transactions": { "$each": [{ "amount": amount, "added": date }], "$sort": { "added": -1 } } } } ); 

另一个“调整”部分是桶概念。 虽然基本上这样做与数组是一个好主意,使用$inc使事务的一部分primefaces,问题是你不希望在数组中的项目很多。 随着时间的推移,这将会大大增加。

要做到这一点的最好方法是只保留这个数组中的这么多项目,并将这些项目限制为“bucketed”结果。 我也在这里增加了一些处理,至less在“尝试”保持单一的“平衡”点同步,但实际上,你可能想要定期validation,因为结果多个更新不受交易约束。

但“桶”的更新是primefaces的。 里程可能会有所不同,实际执行,但这里是本质的示范代码:

 var async = require('async'), mongoose = require('mongoose'), Schema = mongoose.Schema; mongoose.connect('mongodb://localhost/shop'); var ownerSchema = new Schema({ name: String, email: String, accounts: [{ type: Schema.Types.ObjectId, ref: "Account" }] }); var transactionSchema = new Schema({ amount: Number, added: Date }); var recentBucketSchema = new Schema({ _id: { type: Schema.Types.ObjectId, ref: "AccountBucket" }, day: Date }); var accountSchema = new Schema({ owner: { type: Schema.Types.ObjectId, ref: "Owner" }, balance: { type: Number, default: 0 }, recent: [recentBucketSchema] }); var accountBucketSchema = new Schema({ day: Date, account: { type: Schema.Types.ObjectId, ref: "Account" }, owner: { type: Schema.Types.ObjectId, ref: "Owner" }, balance: { type: Number, default: 0 }, transactions: [transactionSchema] }); var Owner = mongoose.model( "Owner", ownerSchema ); var Account = mongoose.model( "Account", accountSchema ); var AccountBucket = mongoose.model( "AccountBucket", accountBucketSchema ); var owner = new Owner({ name: "bill", emal: "bill@test.com" }); var account = new Account({ owner: owner }); owner.accounts.push(account); var transact = function(accId,owner,amount,date,callback) { var day = new Date( date.valueOf() - (date.valueOf() % (1000 * 60 * 60 * 24)) ); var bucket = AccountBucket.collection.initializeOrderedBulkOp(); var account = Account.collection.initializeOrderedBulkOp(); bucket.find({ "account": accId, "owner": owner, "day": day }).upsert().updateOne( { "$inc": { "balance": amount }, "$push": { "transactions": { "$each": [{ "amount": amount, "added": date }], "$sort": { "added": -1 } } } } ); bucket.execute(function(err,response) { if (err) throw err; var upObj = { "$inc": { "balance": amount } }; if ( response.nUpserted > 0 ) { var id = response.getUpsertedIds()[0]._id; upObj["$push"] = { "recent": { "$each": [{ "_id": id, "day": day }], "$sort": { "day": -1 }, "$slice": 30 } }; } console.log( JSON.stringify( upObj, undefined, 4 ) ); account.find({ "_id": accId }).updateOne(upObj); account.execute(function(err,response) { callback(err,response); }); } ); }; mongoose.connection.on("open",function(err,conn) { async.series([ function(callback) { async.each([Owner,Account,AccountBucket],function(model,complete) { model.remove(function(err) { if (err) throw err; complete(); }); },function(err) { if (err) throw err; callback(); }); }, function(callback) { async.each([account,owner],function(model,complete) { model.save(function(err) { if (err) throw err; complete(); }); },function(err) { if (err) throw err; callback(); }); }, function(callback) { var trandate = new Date(); transact(account._id,owner._id,10,trandate,function(err,response) { if (err) throw err; console.log( JSON.stringify( response, undefined, 4 ) ); callback(); }); }, function(callback) { var trandate = new Date(); trandate = new Date( trandate.valueOf() + ( 1000 * 60 * 60 * 1 ) ); transact(account._id,owner._id,-5,trandate,function(err,response) { if (err) throw err; console.log( JSON.stringify( response, undefined, 4 ) ); callback(); }); }, function(callback) { var trandate = new Date(); trandate = new Date( trandate.valueOf() - ( 1000 * 60 * 60 * 1 ) ); transact(account._id,owner._id,15,trandate,function(err,response) { if (err) throw err; console.log( JSON.stringify( response, undefined, 4 ) ); callback(); }); }, function(callback) { var trandate = new Date(); trandate = new Date( trandate.valueOf() - ( 1000 * 60 * 60 * 24 ) ); transact(account._id,owner._id,-5,trandate,function(err,response) { if (err) throw err; console.log( JSON.stringify( response, undefined, 4 ) ); callback(); }); }, function(callback) { var trandate = new Date("2014-07-02"); transact(account._id,owner._id,10,trandate,function(err,response) { if (err) throw err; console.log( JSON.stringify( response, undefined, 4 ) ); callback(); }); }, ],function(err) { String.prototype.repeat = function( num ) { return new Array( num + 1 ).join( this ); }; console.log( "Outputs\n%s\n", "=".repeat(80) ); async.series([ function(callback) { Account.findById(account._id,function(err,account) { if (err) throw err; console.log( "Raw Account\n%s\n%s\n", "=".repeat(80), JSON.stringify( account, undefined, 4 ) ); callback(); }); }, function(callback) { AccountBucket.find({},function(err,buckets) { if (err) throw err; console.log( "Buckets\n%s\n%s\n", "=".repeat(80), JSON.stringify( buckets, undefined, 4 ) ); callback(); }); }, function(callback) { Account.findById(account._id) .populate("owner recent._id") .exec(function(err,account) { if (err) throw err; var raw = account.toObject(); raw.transactions = []; raw.recent.forEach(function(recent) { recent._id.transactions.forEach(function(transaction) { raw.transactions.push( transaction ); }); }); delete raw.recent; console.log( "Merged Pretty\n%s\n%s\n", "=".repeat(80), JSON.stringify( raw, undefined, 4 ) ); callback(); }); } ],function(err) { process.exit(); }); }); }); 

此列表使用MongoDB 2.6提供的“批量”更新APIfunction,但您不必使用它。 只是在这里从更新中转储出更有意义的响应。

“分拆”交易的一般情况是,您将要以某种方式拆分它们。 这里的基本例子是“日”,但是其他可能是更实际的。

为了确保在标识符更改时创build新的存储桶,使用了MongoDB更新的“upsert”function。 这应该是一般自行,因为你可以稍后在所有“桶”之间运行平衡,但在这种情况下,我们将至less“尝试”,以保持一个“帐户”主同步,如果只是多一点示范。

当前桶的更新完成后,检查响应是否发生“upsert”。 在遗留或mongooseAPI .update()这将只是在callback中的第三个参数返回“upserted”文档的_id

如果发生“upsert”并创build了一个新的桶,我们也会把它添加到主“Account”中作为最近桶的列表,实际上是最近的桶。 所以这次$push操作使用额外的$slice修饰符来执行其他$each$sort操作。

即使只有一个数组元素要添加,最后两个也需要一起使用。 MongoDB 2.4版本实际上总是需要使用这些修饰符的$slice ,所以如果你不想限制的话,可以把$slice设置成一个很大的数字,但是限制数组的长度是个好习惯。

在每种情况下,尽pipe所有示例代码都插入了这些date,但date仍按最近的第一个sorting。 输出将以这种forms向您显示在写入操作中实际发生的所有事情,但总体最终结果的摘要在此用于阅读目的:

 Outputs ======================================================================== Raw Account ======================================================================== { "_id": "53bf504ac0716cbc113fbac5", "owner": "53bf504ac0716cbc113fbac4", "__v": 0, "recent": [ { "_id": "53bf504a79b21601f0c00d1d", "day": "2014-07-11T00:00:00.000Z" }, { "_id": "53bf504a79b21601f0c00d1e", "day": "2014-07-10T00:00:00.000Z" }, { "_id": "53bf504a79b21601f0c00d1f", "day": "2014-07-02T00:00:00.000Z" } ], "balance": 25 } Buckets ======================================================================== [ { "_id": "53bf504a79b21601f0c00d1d", "account": "53bf504ac0716cbc113fbac5", "day": "2014-07-11T00:00:00.000Z", "owner": "53bf504ac0716cbc113fbac4", "transactions": [ { "amount": -5, "added": "2014-07-11T03:47:38.170Z" }, { "amount": 10, "added": "2014-07-11T02:47:38.153Z" }, { "amount": 15, "added": "2014-07-11T01:47:38.176Z" } ], "balance": 20 }, { "_id": "53bf504a79b21601f0c00d1e", "account": "53bf504ac0716cbc113fbac5", "day": "2014-07-10T00:00:00.000Z", "owner": "53bf504ac0716cbc113fbac4", "transactions": [ { "amount": -5, "added": "2014-07-10T02:47:38.182Z" } ], "balance": -5 }, { "_id": "53bf504a79b21601f0c00d1f", "account": "53bf504ac0716cbc113fbac5", "day": "2014-07-02T00:00:00.000Z", "owner": "53bf504ac0716cbc113fbac4", "transactions": [ { "amount": 10, "added": "2014-07-02T00:00:00.000Z" } ], "balance": 10 } ] Merged Pretty ======================================================================== { "_id": "53bf504ac0716cbc113fbac5", "owner": { "_id": "53bf504ac0716cbc113fbac4", "name": "bill", "__v": 0, "accounts": [ "53bf504ac0716cbc113fbac5" ] }, "__v": 0, "balance": 25, "transactions": [ { "amount": -5, "added": "2014-07-11T03:47:38.170Z" }, { "amount": 10, "added": "2014-07-11T02:47:38.153Z" }, { "amount": 15, "added": "2014-07-11T01:47:38.176Z" }, { "amount": -5, "added": "2014-07-10T02:47:38.182Z" }, { "amount": 10, "added": "2014-07-02T00:00:00.000Z" } ] }