首页 > 解决方案 > 您如何播种 mongodb 数据库,以便 Keystone 5 CMS 识别多对多关系?

问题描述

假设我有两个对象:Product 和 Seller

产品可以有多个卖家。一个卖家可以销售多个产品。

目标是编写一个播种脚本,成功播种我的 MongoDB 数据库,以便 Keystone.js 的 CMS 识别多对多关系。

模式

Product.ts

import { text, relationship } from "@keystone-next/fields";
import { list } from "@keystone-next/keystone/schema";

export const Product = list({
  fields: {
    name: text({ isRequired: true }),
    sellers: relationship({
      ref: "Seller.products",
      many: true,
    }),
  },
});

Seller.ts

import { text, relationship } from "@keystone-next/fields";
import { list } from "@keystone-next/keystone/schema";

export const Product = list({
  fields: {
    name: text({ isRequired: true }),
    products: relationship({
      ref: "Product.sellers",
      many: true,
    }),
  },
});

KeystoneJS 配置

为了简洁起见,我的keystone.ts配置如下所示:

import { insertSeedData } from "./seed-data"
...
db: {
  adapter: "mongoose",
  url: databaseURL,
  async onConnect(keystone) {
    console.log("Connected to the database!");
    if (process.argv.includes("--seed-data")) {
      await insertSeedData(keystone);
    }
  },
},
lists: createSchema({
  Product,
  Seller,
}),
...

播种脚本(这些是我希望更改的文件)

我有一个填充数据库(seed-data/index.ts)的脚本:

import { products } from "./data";
import { sellers } from "./data";

export async function insertSeedData(ks: any) {

  // setup code
  const keystone = ks.keystone || ks;
  const adapter = keystone.adapters?.MongooseAdapter || keystone.adapter;
  const { mongoose } = adapter;
  mongoose.set("debug", true);

  // adding products to DB
  for (const product of products) {
    await mongoose.model("Product").create(product);
  }

  // adding sellers to DB
  for (const seller of sellers) {
    await mongoose.model("Seller").create(seller);
  }
}

最后,data.ts看起来像这样:

export const products = [
  {
    name: "apple",
    sellers: ["Joe", "Anne", "Duke", "Alicia"],
  },
  {
    name: "orange",
    sellers: ["Duke", "Alicia"],
  },
  ...
];
export const sellers = [
  {
    name: "Joe",
    products: ["apple", "banana"],
  },
  {
    name: "Duke",
    products: ["apple", "orange", "banana"],
  },
  ...
];

由于各种原因,上述设置不起作用。最明显的是and对象的sellersandproducts属性(分别)应该引用对象()而不是名称(例如“apple”、“Joe”)。ProductSellerObjectId

我将在下面发布一些我认为可行的尝试,但没有:

尝试 1

我想我只是给他们临时 ID(下面的id属性data.ts),然后,一旦 MongoDB 分配了ObjectId,我就会使用它们。

seed-data/index.ts

...
  const productIdsMapping = [];
...
  // adding products to DB
  for (const product of products) {
    const productToPutInMongoDB = { name: product.name };
    const { _id } = await mongoose.model("Product").create(productToPutInMongoDB);
    productIdsMapping.push(_id);
  }

  // adding sellers to DB (using product IDs created by MongoDB)
  for (const seller of sellers) {
    const productMongoDBIds = [];
    for (const productSeedId of seller.products) {
      productMongoDBIds.push(productIdsMapping[productSeedId]);
    const sellerToPutInMongoDB = { name: seller.name, products: productMongoDBIds };
    await mongoose.model("Seller").create(sellerToPutInMongoDB);
  }
...

data.ts

export const products = [
  {
    id: 0,
    name: "apple",
    sellers: [0, 1, 2, 3],
  },
  {
    id: 1,
    name: "orange",
    sellers: [2, 3],
  },
  ...
];
export const sellers = [
  {
    id: 0
    name: "Joe",
    products: [0, 2],
  },
  ...
  {
    id: 2
    name: "Duke",
    products: [0, 1, 2],
  },
  ...
];

输出(尝试1):

它似乎并不关心或承认该products属性。

Mongoose: sellers.insertOne({ _id: ObjectId("$ID"), name: 'Joe', __v: 0}, { session: null })
{
  results: {
    _id: $ID,
    name: 'Joe',
    __v: 0
  }
}

尝试 2

我想也许我只是因为某种原因没有正确格式化它,所以如果我查询产品并将它们直接推到卖方对象中,那可能会奏效。

seed-data/index.ts

...
  const productIdsMapping = [];
...
  // adding products to DB
  for (const product of products) {
    const productToPutInMongoDB = { name: product.name };
    const { _id } = await mongoose.model("Product").create(productToPutInMongoDB);
    productIdsMapping.push(_id);
  }

  // adding sellers to DB (using product IDs created by MongoDB)
  for (const seller of sellers) {
    const productMongoDBIds = [];
    for (const productSeedId of seller.products) {
      productMongoDBIds.push(productIdsMapping[productSeedId]);
    }
    const sellerToPutInMongoDB = { name: seller.name };
    const { _id } = await mongoose.model("Seller").create(sellerToPutInMongoDB);
    const resultsToBeConsoleLogged = await mongoose.model("Seller").findByIdAndUpdate(
      _id,
      {
        $push: {
          products: productMongoDBIds,
        },
      },
      { new: true, useFindAndModify: false, upsert: true }
    );
  }
...

data.ts

与尝试 1 相同的 data.ts 文件。

输出(尝试2):

一样。products出现的属性没有运气。

Mongoose: sellers.insertOne({ _id: ObjectId("$ID"), name: 'Joe', __v: 0}, { session: null })
{
  results: {
    _id: $ID,
    name: 'Joe',
    __v: 0
  }
}

所以,现在我被困住了。我认为尝试 1 会像这个答案一样 Just Work™:

https://stackoverflow.com/a/52965025

有什么想法吗?

标签: node.jsmongodbmongoosekeystonejs

解决方案


我想出了一个解决办法。这是背景:

当我定义架构时,Keystone 会创建相应的 MongoDB 集合。如果对象 A 和对象 B 之间存在多对多关系,Keystone 将创建 3 个集合:A、B 和 A_relationshipToB_B_relationshipToA。

第三个集合是两者之间的接口。它只是一个包含来自 A 和 B 的 id 对的集合。

因此,为了使用 Keystone CMS 中显示的多对多关系为我的数据库播种,我不仅需要播种 A 和 B,还需要播种第三个集合:A_relationshipToB_B_relationshipToA。

因此,seed-data/index.ts将有一些代码插入到该表中:

...
for (const seller of sellers) {
    const sellerToAdd = { name: seller.name };
    const { _id } = await mongoose.model("Seller").create(sellerToAdd);

    // Product_sellers_Seller_products Insertion
    for (const productId of seller.products) {
      await mongoose
        .model("Product_sellers_Seller_products")
        .create({
          Product_left_id: productIds[productId], // (data.ts id) --> (Mongo ID)
          Seller_right_id: _id,
        });
    }
  }
...

推荐阅读