Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/426.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 使用mongoose对子文档数组进行复杂添加_Javascript_Node.js_Mongodb_Mongoose_Atomic - Fatal编程技术网

Javascript 使用mongoose对子文档数组进行复杂添加

Javascript 使用mongoose对子文档数组进行复杂添加,javascript,node.js,mongodb,mongoose,atomic,Javascript,Node.js,Mongodb,Mongoose,Atomic,我的数据模型有账户,账户有一些信用交易。我已将这些交易设计为子文档: var TransactionSchema = new Schema({ amount: Number, added: Date }), AccountSchema = new Schema({ owner: ObjectId, balance: Number, transactions: [TransactionSchema]

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

var TransactionSchema = new Schema({
        amount: Number,
        added: Date
    }),
    AccountSchema = new Schema({
        owner: ObjectId,
        balance: Number,
        transactions: [TransactionSchema]
    });
交易
添加到
帐户
时,应发生以下情况:

  • 事务
    已将新事务推送到它
  • 交易
    按日期排序(供以后显示)
  • 余额
    设置为所有
    交易的总和
我已经把它放在一个
模式中了。现在,在保存之前,用JavaScript执行上述操作。但是,我不确定同时进行多个插入是否安全


在Mongoose中,如何使用原子更新或某种事务更新更好地解决这个问题?在SQL中,我只想做一个事务,但在MongoDB中我做不到,所以如何确保
事务
余额
总是正确的?

您可以通过一个
更新
调用来完成所有这些操作(这是使更新组合原子化的唯一方法)。在更新过程中,您不会对交易进行求和,而是使用更改的金额更新
余额

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);

请注意,这确实利用了
$push
的修饰符,该修饰符在2.4中添加,并在2.6中更新,因此您需要使用最新的版本。

您可以通过一个
update
调用来完成所有这些操作,该调用组合了所有这三个操作(这是使更新组合成为原子的唯一方法)。在更新过程中,您不会对交易进行求和,而是使用更改的金额更新
余额

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);

请注意,这确实使用了
$push
的修饰符,该修饰符是在2.4中添加的,在2.6中更新的,因此您需要使用最新的版本。

与刚才的答案大致相同,但我有一个较长的解释,因此需要一段时间

所以,再做一次调整,情况基本相同。使用余额上的运算符进行交易是您想要的,而不是重新计算,因此基本上这段代码:

  bucket.find({
    "account": accId, "owner": owner, "day": day
  }).upsert().updateOne(
    {
      "$inc": { "balance": amount },
      "$push": {
        "transactions": {
          "$each": [{ "amount": amount, "added": date }],
          "$sort": { "added": -1 }
        }
      }
    }
  );
另一个“调整”部分是桶的概念。虽然基本上对数组执行此操作是一个好主意,并且使用
$inc
使事务的这一部分原子化,但问题是您不希望数组中有很多项。随着时间的推移,这将大大增加

最好的方法是在该数组中只保留这么多项,并将这些项限制为“带扣”的结果。我在这里还添加了更多的处理,至少“尝试”保持一个单一的“平衡”点同步,但实际上,您可能希望定期验证,因为生成的多个更新不受事务的约束

但“桶”的更新是原子的。里程数可能因实际实施而异,但以下是基本演示代码:

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提供的“批量”更新API功能,但您不必使用它。这里只是为了从更新中转储更有意义的响应

通常情况下,当“bucketing”交易时,您将以某种方式将其拆分。这里的基本示例是“day”,但可能还有其他更实际的例子

为了确保在标识符更改时创建新的bucket,使用了MongoDB updates的“upsert”功能。这本身应该是正常的,因为您可以稍后在所有“bucket”之间获得一个运行平衡,但在这种情况下,我们至少要“尝试”保持“Account”master的同步,哪怕只是为了进行更多的演示

在当前bucket的更新完成后,将检查响应以查看是否发生了“upsert”。在legacy或mongoose API下,这将在回调的第三个参数中返回“upserted”文档的
\u id

如果发生“upsert”并创建了一个新的bucket,我们还将把它添加到主“Account”中,作为最近bucket的列表,实际上是最近30个bucket的列表。因此,这次操作对其他and操作使用了一个额外的修饰符

即使只有一个数组元素要添加,最后两个元素也需要一起使用。MongoDB 2.4版本实际上需要始终使用这些修饰符使用
$slice
,因此如果您不想限制,请将
$slice
设置为一个较大的数字,但限制数组的长度是一个很好的做法

在每种情况下,尽管所有示例代码插入日期的顺序不同,日期都以最近的第一个日期排序。输出将以这种形式向您显示写入操作中实际发生的所有情况,但此处是一般最终结果的摘要,仅供阅读:

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"
        }
    ]
}

我的回答和刚才的答案差不多,但我有一个较长的解释,所以需要一段时间

所以,再做一次调整,情况基本相同。使用余额上的运算符进行交易是您想要的,而不是重新计算,因此基本上这段代码:

  bucket.find({
    "account": accId, "owner": owner, "day": day
  }).upsert().updateOne(
    {
      "$inc": { "balance": amount },
      "$push": {
        "transactions": {
          "$each": [{ "amount": amount, "added": date }],
          "$sort": { "added": -1 }
        }
      }
    }
  );
另一个“调整”部分是桶的概念。虽然基本上对数组执行此操作是一个好主意,并且使用
$inc
使事务的这一部分原子化,但问题是您不希望数组中有很多项。随着时间的推移,这将大大增加

最好的方法是在该数组中只保留这么多项,并将这些项限制为“带扣”的结果。我在这里还添加了更多的处理,至少“尝试”保持一个单一的“平衡”点同步,但实际上,您可能希望定期验证,因为生成的多个更新不受事务的约束

但“桶”的更新是原子的。里程