【问题标题】:Create ID with data of other fields使用其他字段的数据创建 ID
【发布时间】:2016-08-06 08:44:18
【问题描述】:

我是 mongodb 的新手,我正在使用 mongoose 来验证和排序数据(如果这不起作用,我愿意将其更改为 MySQL)。 该应用程序将是一个电子商店,用于购买与电影、游戏等相关的商品。

我的架构如下:

var productSchema = {
    id: {
        type: String,
        required: true
    },
    name: {
        type: String,
        required: true
    },
    img: {
        type: String,
        required: true
    },
    price: {
        type: Number,
        required: true
    },
    stock: {
        type: Number,
        required: true
    },
    category: {
        object: {
            type: String,
            required: true
        },
        group: {
            type: String,
            required: true  
        },
        name: {
            type: String,
            required: true
        }
    }
};

这是我想做的:

如果我在类别中有以下数据:

  • category.object = "戒指"
  • category.group = "电影"
  • category.name="指环王"

我希望 id 由类别中每个字段的第一个字母和一个数字(添加的最后一项的数字加 1)组成。在这种情况下,它将是 RMLOTR1。

我现在在做什么

我同时添加了很多数据,所以每次我这样做时,我都会创建一个函数来遍历所有添加的项目并执行我想要的操作,但是......

我的问题是

有没有一种内置的方法可以使用 mongodb 或 mongoose 来执行此操作,同时添加数据和创建 id?我知道我可以做一个虚拟的,但我想存储数据。

附加内容

  • 如果使用 mongodb 无法做到这一点,有没有办法使用 MySQL 做到这一点?
  • 做这种事情是否被认为是正确/错误的方法?

【问题讨论】:

    标签: javascript node.js mongodb mongoose mongodb-query


    【解决方案1】:

    您基本上是在寻找"pre" middleware 挂钩,该挂钩是通过在集合中创建新文档而触发的。这将检查当前文档内容并从值中提取“字符串”,以便为_id 创建“前缀”值。

    还有另一部分,其中“前缀”需要添加数字计数器,当该特定“前缀”已经存在一个值以使其不同时。 "Generate an auto-incrementing sequence field" 在 MongoDB 中有一个常用技术,它基本上涉及保持一个“计数器”集合并在每次访问它时递增值。

    作为一个完整且独立的演示,您将以下技术结合起来:

    var async = require('async'),
        mongoose = require('mongoose'),
        Schema = mongoose.Schema;
    
    mongoose.connect('mongodb://localhost/warehouse');
    
    var counterSchema = new Schema({
      "type": { "type": String, "required": true },
      "prefix": { "type": String, "required": true },
      "counter": Number
    });
    
    counterSchema.index({ "type": 1, "prefix": 1 },{ "unique": true });
    
    counterSchema.virtual('nextId').get(function() {
      return this.prefix + this.counter;
    });
    
    var productSchema = new Schema({
      "_id": "String",
      "category": {
        "object": { "type": String, "required": true },
        "group": { "type": String, "required": true },
        "name": { "type": String, "required": true }
      }
    },{ "_id": false });
    
    productSchema.pre('save', function(next) {
      var self = this;
    
      if ( !self.hasOwnProperty("_id") ) {
    
        var prefix = self.category.object.substr(0,1).toUpperCase()
          + self.category.group.substr(0,1).toUpperCase()
          + self.category.name.split(" ").map(function(word) {
            return word.substr(0,1).toUpperCase();
          }).join("");
    
        mongoose.model('Counter').findOneAndUpdate(
          { "type": "product", "prefix": prefix },
          { "$inc": { "counter": 1 } },
          { "new": true, "upsert": true },
          function(err,counter) {
            self._id = counter.nextId;
            next(err);
          }
        );
      } else  {
        next();  // Just skip when _id is already there
      }
    });
    
    var Product = mongoose.model('Product',productSchema),
        Counter = mongoose.model('Counter', counterSchema);
    
    async.series(
      [
        // Clean data
        function(callback) {
          async.each([Product,Counter],function(model,callback) {
            model.remove({},callback);
          },callback);
        },
        function(callback) {
          async.each(
            [
              {
                "category": {
                  "object": "ring",
                  "group": "movies",
                  "name": "lord of the rings"
                }
              },
              {
                "category": {
                  "object": "ring",
                  "group": "movies",
                  "name": "four weddings and a funeral"
                }
              },
              {
                "category": {
                  "object": "ring",
                  "group": "movies",
                  "name": "lord of the rings"
                }
              }
            ],
            function(data,callback) {
              Product.create(data,callback)
            },
            callback
          )
        },
        function(callback) {
          Product.find().exec(function(err,products) {
            console.log(products);
            callback(err);
          });
        },
        function(callback) {
          Counter.find().exec(function(err,counters) {
            console.log(counters);
            callback(err);
          });
        }
      ],
      function(err) {
        if (err) throw err;
        mongoose.disconnect();
      }
    )
    

    这会给你这样的输出:

    [ { category: { name: 'lord of the rings', group: 'movies', object: 'ring' },
        __v: 0,
        _id: 'RMLOTR1' },
      { category:
         { name: 'four weddings and a funeral',
           group: 'movies',
           object: 'ring' },
        __v: 0,
        _id: 'RMFWAAF1' },
      { category: { name: 'lord of the rings', group: 'movies', object: 'ring' },
        __v: 0,
        _id: 'RMLOTR2' } ]
    [ { __v: 0,
        counter: 2,
        type: 'product',
        prefix: 'RMLOTR',
        _id: 57104cdaa774fcc73c1df0e8 },
      { __v: 0,
        counter: 1,
        type: 'product',
        prefix: 'RMFWAAF',
        _id: 57104cdaa774fcc73c1df0e9 } ]
    

    要首先了解Counter 模式和模型,您基本上是在定义一些内容,您将在其中查找“唯一”键并在匹配时将数字字段附加到“增量”。为方便起见,它只有一个组成唯一组合的两个字段和一个定义的复合索引。如果需要,这也可以是一个复合 _id

    另一个方便之处是nextIdvirtual 方法,它只是将“前缀”和“计数器”值串联起来。在此处包含“类型”之类的内容也是最佳实践,因为您的Counter 模型可用于服务“计数器”以用于多个收集源。因此,在这里我们在访问 Product 模型的上下文时使用 "product" 以将其与您可能也保留类似序列计数器的其他模型区分开来。只是一个值得关注的设计点。

    对于实际的Product 模型本身,我们要附加“预保存”中间件挂钩以填充_id 内容。因此,在确定了“前缀”的字符部分之后,该操作开始执行并在 Counter 模型集合中查找该“前缀”与“产品”类型数据的组合。

    .findOneAndUpdate() 的功能是在 "counters" 集合中查找符合条件的文档,然后在已经找到文档的地方,它将通过使用 $inc 来“增加”当前的 counter 值更新运算符。如果未找到该文档,则"upsert" 选项意味着将创建一个新文档,并且无论如何,新文档中也会发生相同的“增量”。

    这里的"new" 选项意味着我们希望返回“修改后的”文档(新的或更改的),而不是应用$inc 之前文档的样子。结果是“计数器”值将始终在每次访问时增加。

    一旦完成并且Counter 的文档被递增或创建为它的匹配键,那么您现在可以使用一些东西来分配给Product 模型中的_id。如前所述,您可以在此处使用virtual 方便获取带有附加计数器值的前缀。

    只要您的文档始终是由模型中的 .create() 方法或使用 new Product() 然后使用 .save() 方法创建的,那么附加到代码中“模型”的方法始终是执行。

    请注意,既然您希望在_id 中使用它,那么作为主键,它是“不可变的”并且无法更改。因此,即使引用的字段中的内容后来被更改,_id 中的值也无法更改,因此这里的代码在 _id 值已经设置时不进行尝试。

    【讨论】:

    • 哇!太感谢了!我整天都在试图弄清楚这一点,但没有运气,但你让它真的很容易理解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-19
    • 2020-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-10
    相关资源
    最近更新 更多