首页 > 解决方案 > Mongoose 更新相互关系

问题描述

我正在使用 2 个模型:

产品和事件。

产品模型具有来自事件 (_id) 的引用事件模型具有来自产品 (_id) 的引用。

到目前为止,我发现如果我想更新一个事件并添加或退出产品,我必须使用另一个操作来更新产品模型。删除也是如此(如果我删除一个事件,我必须更新与该事件关联的产品并将它们取出)。

我的问题是,例如:我有一个具有 event_id:[1,2,3,4}] 的产品(id 1) 我有一个具有 product_id:[1,2] 的事件(id 1)。

如果我更新事件(id 1)并取出产品 1,则事件现在将只有产品 id 2,但产品仍然有事件 id 1。

为此,我正在使用钩子:特别是为了更新我正在这样做:

EventSchema.post("findOneAndUpdate", function(doc) {
  Product.updateMany({
    _id: {
      $in: doc.product
    }
  }, {
    event: doc._id.toString()
  }, {
    multi: true
  }, function(error, product) {
    if (error) console.log(error)
  })
})

如何更新 Product 模型,取出 event_id 列数组中的 id?我希望我的解释清楚。

事件 希玛

const EventSchema = new Schema({

   client: {
   type: [{
      type: Schema.Types.ObjectId,
     ref: 'Client'
   }]
  },

  product: {
   type: [{
      type: Schema.Types.ObjectId,
      ref: 'Product'
    }]
  },

     date: {
    type: Date,
    maxlength: 64,
     lowercase: true,
   trim: true
  },

  place: {
   type: String,
    maxlength: 1200,
   minlength: 1,
 },

  price: {
   type: Number
  },

  comment: {
    type: String,
   maxlength: 12000,
   minlength: 1,
  },
},
 {
   toObject: { virtuals: true },
    toJSON: { virtuals: true }
 },
  {
   timestamps: true
 },
);

客户端架构

const ClientSchema = new Schema(
  {
    first_name: {
      type: String,
      maxlength: 64,
      minlength: 1,
      required: [true, "Client first name is required"]
    },

    last_name: {
      type: String,
      maxlength: 64,
      minlength: 1
    },

    address: {
      type: String,
      maxlength: 1200,
      minlength: 1
    },

    phone: {
      type: String,
      maxlength: 64,
      minlength: 1
    },

    email: {
      type: String,
      maxlength: 64,
      lowercase: true,
      trim: true
    },

    web: {
      type: String,
      maxlength: 1200,
      minlength: 1
    },

    comment: {
      type: String,
      maxlength: 12000,
      minlength: 1
    },

    event: {
      type: [
        {
          type: Schema.Types.ObjectId,
          ref: "Event"
        }
      ]
    }
  },
  {
    toObject: { virtuals: true },
    toJSON: { virtuals: true }
  },
  {
    timestamps: true
  }
);

产品架构:

const ProductSchema = new Schema(
  {
    name: {
      type: String,
      maxlength: 64,
      minlength: 1,
      required: [true, "Product name is required"]
    },

    description: {
      type: String,
      maxlength: 12000,
      minlength: 1
    },

    comment: {
      type: String,
      maxlength: 12000,
      minlength: 1
    },

    state: {
      type: String,
      maxlength: 64,
      minlength: 0
    },

    available: {
      type: Boolean,
      default: true
    },
    price: {
      type: Number
    },

    category: {
      type: [
        {
          type: Schema.Types.ObjectId,
          ref: "Category"
        }
      ]
    },

    event: {
      type: [
        {
          type: Schema.Types.ObjectId,
          ref: "Event"
        }
      ]
    },
    image: {
      type: [
        {
          type: Schema.Types.ObjectId,
          ref: "Image"
        }
      ]
    }
  },
  {
    toObject: { virtuals: true },
    toJSON: { virtuals: true }
  },
  {
    timestamps: true
  }
);

输入数据:product/:id route

{
"available": false,
"category": [
    "5e04cd609b1c6812c8f9a9d1"
],
"event": [
    "5e07f73d72503f6659f40b26"
],
"image": [
    "5e05f9dd66432544b7bf0221"
],
"_id": "5e05f9d166432544b7bf0220",
"name": "Mesa ratona",
"price": 1200,
"comment": "-",
"description": "-",
"__v": 0,
"modelName": "Product",
"id": "5e05f9d166432544b7bf0220"

}

输入数据事件/:id

{
"client": [
    "5e05f743cd57804386a92a89"
],
"product": [
    "5e05f9d166432544b7bf0220",
    "5e06464b811a954e831eafcf",
    "5e064c5c811a954e831eafd3"
],
"_id": "5e07f73d72503f6659f40b26",
"date": "2020-01-12T00:45:33.069Z",
"place": "EVENT 1",
"price": 123,
"comment": "EVENT 1",
"__v": 0,
"id": "5e07f73d72503f6659f40b26"

}

输入数据客户端/:id

{
"event": [
    "5e07f73d72503f6659f40b26"
],
"_id": "5e05f743cd57804386a92a89",
"first_name": "Ernesto",
"last_name": "De Lucía",
"address": "Calle Santa Fé 3129 Piso 5 Depto \"B\" Capital Federal",
"email": "ernestodl@gmail.com",
"phone": "+54 011 1567432984",
"web": "-",
"comment": "-",
"__v": 0,
"full_name": "Ernesto De Lucía",
"id": "5e05f743cd57804386a92a89"

}

谢谢!

标签: node.jsexpressmongoosemongoose-schema

解决方案


在您的模式中,有许多引用,因此当需要删除一个项目时,需要从所有引用的文档中删除它。

我建议您使用virtual populate删除多对多的引用。

我简化了模式以仅包含必填字段。

事件:(我删除了对产品和客户的引用,并使用了虚拟填充)

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const EventSchema = new Schema(
  {
    name: {
      type: String,
      required: true
    }
  },
  {
    toObject: { virtuals: true },
    toJSON: { virtuals: true }
  },
  {
    timestamps: true
  }
);

// Virtual populate
EventSchema.virtual("clients", {
  ref: "Client",
  foreignField: "events",
  localField: "_id"
});

EventSchema.virtual("products", {
  ref: "Product",
  foreignField: "events",
  localField: "_id"
});

module.exports = mongoose.model("Event", EventSchema);

客户:

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const ClientSchema = new Schema(
  {
    first_name: {
      type: String,
      maxlength: 64,
      minlength: 1,
      required: [true, "Client first name is required"]
    },
    events: [
      {
        type: Schema.Types.ObjectId,
        ref: "Event"
      }
    ]
  },
  {
    toObject: { virtuals: true },
    toJSON: { virtuals: true }
  },
  {
    timestamps: true
  }
);

module.exports = mongoose.model("Client", ClientSchema);

产品:

const mongoose = require("mongoose");
const Schema = mongoose.Schema;

const ProductSchema = new Schema(
  {
    name: {
      type: String,
      maxlength: 64,
      minlength: 1,
      required: [true, "Product name is required"]
    },
    events: [
      {
        type: Schema.Types.ObjectId,
        ref: "Event"
      }
    ]
  },
  {
    toObject: { virtuals: true },
    toJSON: { virtuals: true }
  },
  {
    timestamps: true
  }
);

module.exports = mongoose.model("Product", ProductSchema);

我有这些示例文件:

事件:

{
    "_id": "5e08ceb5a62f094ba4ca2542",
    "name": "Event 1",
    "__v": 0,
    "id": "5e08ceb5a62f094ba4ca2542"
}
{
    "_id": "5e08cec5a62f094ba4ca2543",
    "name": "Event 2",
    "__v": 0,
    "id": "5e08cec5a62f094ba4ca2543"
}

产品:

{
    "events": [
        "5e08ceb5a62f094ba4ca2542",
        "5e08cec5a62f094ba4ca2543"
    ],
    "_id": "5e08cf9de9dfcc0e1431c90b",
    "name": "Product 1",
    "__v": 0,
    "id": "5e08cf9de9dfcc0e1431c90b"
}
{
    "events": [
        "5e08ceb5a62f094ba4ca2542"
    ],
    "_id": "5e08cfa9e9dfcc0e1431c90c",
    "name": "Product 2",
    "__v": 0,
    "id": "5e08cfa9e9dfcc0e1431c90c"
}

客户:

{
    "events": [
        "5e08ceb5a62f094ba4ca2542",
        "5e08cec5a62f094ba4ca2543"
    ],
    "_id": "5e08cfdce9dfcc0e1431c90e",
    "first_name": "Client 1",
    "__v": 0,
    "id": "5e08cfdce9dfcc0e1431c90e"
}
{
    "events": [
        "5e08ceb5a62f094ba4ca2542"
    ],
    "_id": "5e08cfece9dfcc0e1431c90f",
    "first_name": "Client 2",
    "__v": 0,
    "id": "5e08cfece9dfcc0e1431c90f"
}

让我们首先解决从事件中访问客户和产品的问题,因为我们删除了这些引用。

我们可以使用以下路由来获取虚拟填充其客户和产品的事件:

router.get("/events/:id", async (req, res) => {
  const result = await Event.findById(req.params.id)
    .populate("clients")
    .populate("products");

  res.send(result);
});

响应将是这样的:(如您所见,即使我们删除了可以使用虚拟填充访问客户端和产品的引用)

{
    "_id": "5e08ceb5a62f094ba4ca2542",
    "name": "Event 1",
    "__v": 0,
    "clients": [
        {
            "events": [
                "5e08ceb5a62f094ba4ca2542",
                "5e08cec5a62f094ba4ca2543"
            ],
            "_id": "5e08cfdce9dfcc0e1431c90e",
            "first_name": "Client 1",
            "__v": 0,
            "id": "5e08cfdce9dfcc0e1431c90e"
        },
        {
            "events": [
                "5e08ceb5a62f094ba4ca2542"
            ],
            "_id": "5e08cfece9dfcc0e1431c90f",
            "first_name": "Client 2",
            "__v": 0,
            "id": "5e08cfece9dfcc0e1431c90f"
        }
    ],
    "products": [
        {
            "events": [
                "5e08ceb5a62f094ba4ca2542",
                "5e08cec5a62f094ba4ca2543"
            ],
            "_id": "5e08cf9de9dfcc0e1431c90b",
            "name": "Product 1",
            "__v": 0,
            "id": "5e08cf9de9dfcc0e1431c90b"
        },
        {
            "events": [
                "5e08ceb5a62f094ba4ca2542"
            ],
            "_id": "5e08cfa9e9dfcc0e1431c90c",
            "name": "Product 2",
            "__v": 0,
            "id": "5e08cfa9e9dfcc0e1431c90c"
        }
    ],
    "id": "5e08ceb5a62f094ba4ca2542"
}

现在要从产品或客户中删除事件,我们只需要从那里删除即可。

例如要从产品中删除一个事件,我们只需要从产品中删除它:

router.delete("/products/:id/:eventId", async (req, res) => {
  const result = await Product.findByIdAndUpdate(
    req.params.id,
    {
      $pull: {
        events: req.params.eventId
      }
    },
    { new: true }
  );

  res.send(result);
});

在此删除之前,该产品有 2 个事件,其 id5e08ceb5a62f094ba4ca25425e08cec5a62f094ba4ca2543. 删除后,它将只有一个带有 id 的事件5e08ceb5a62f094ba4ca2542

{
    "events": [
        "5e08ceb5a62f094ba4ca2542"
    ],
    "_id": "5e08cf9de9dfcc0e1431c90b",
    "name": "Product 1",
    "__v": 0,
    "id": "5e08cf9de9dfcc0e1431c90b"
}

推荐阅读