许多人使用猫鼬

时间:2017-09-02 23:49:13

标签: javascript node.js mongodb mongoose nosql

我有两个模型:

Item.js

static void Main(string[] args)
    {
        DataTable dt1 = new DataTable();
        dt1.TableName = "winPCT";

        using (WebClient wc1 = new WebClient())
        {
            var json1 = wc1.DownloadString("http://stats.nba.com/stats/leaguestandingsv3?LeagueID=00&Season=2015-16&SeasonType=Regular+Season");
            var winPct = JsonConvert.DeserializeObject<RootObject>(json1);

            dt1.Columns.Add("Team");
            dt1.Columns.Add("WINS");
            dt1.Columns.Add("LOSSES");
            dt1.Columns.Add("WinPCT");
            foreach (var row in winPct.resultSets)
            {
                dt1.Rows.Add(row[2],//  2   Team name
                            row[12],//  12  WINS
                            row[13],//  13  LOSSES
                            row[14]);// 14  WinPCT

            }
        }
        dt1.WriteXml("winpct.xml");
    }

Store.js

const mongoose = require('mongoose');

const itemSchema = new mongoose.Schema({
   name: String,
   stores: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Store' }]
});

const Item = mongoose.model('Item', itemSchema);

module.exports = Item;

seed.js 文件:

const mongoose = require('mongoose');

const storeSchema = new mongoose.Schema({
   name: String,
   items: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Item' }]
});

const Store = mongoose.model('Store', storeSchema);

module.exports = Store;

const faker = require('faker'); const Store = require('./models/Store'); const Item = require('./models/Item'); console.log('Seeding..'); let item = new Item({ name: faker.name.findName() + " Item" }); item.save((err) => { if (err) return; let store = new Store({ name: faker.name.findName() + " Store" }); store.items.push(item); store.save((err) => { if (err) return; }) }); 与包含1 store的{​​{1}}数组一起保存。 items虽然没有item。我错过了什么?如何自动更新MongoDB / Mongoose中的多对多关系?我习惯了Rails,一切都是自动完成的。

2 个答案:

答案 0 :(得分:11)

您目前遇到的问题是您在一个模型中保存了引用但未将其保存在另一个模型中。没有&#34;自动参照完整性&#34;在MongoDB中,以及&#34;关系的概念&#34;真是一个&#34;手册&#34;事件,实际上.populate()的情况实际上是一大堆额外的查询,以便检索引用的信息。没有&#34;魔术&#34;这里。

正确处理&#34;多对多&#34;归结为三个选项:

清单1 - 在两个文档上保留数组

按照您当前的设计,您缺少的部分是将参考文献存储在&#34;两者中&#34;相关项目。有关演示的列表:

const { Schema } = mongoose = require('mongoose');

mongoose.Promise = global.Promise;
mongoose.set('debug',true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

const uri = 'mongodb://localhost:27017/manydemo',
      options = { useNewUrlParser: true };

const itemSchema = new Schema({
  name: String,
  stores: [{ type: Schema.Types.ObjectId, ref: 'Store' }]
});

const storeSchema = new Schema({
  name: String,
  items: [{ type: Schema.Types.ObjectId, ref: 'Item' }]
});

const Item = mongoose.model('Item', itemSchema);
const Store = mongoose.model('Store', storeSchema);


const log = data => console.log(JSON.stringify(data,undefined,2))

(async function() {

  try {

    const conn = await mongoose.connect(uri,options);

    // Clean data
    await Promise.all(
      Object.entries(conn.models).map(([k,m]) => m.deleteMany() )
    );


    // Create some instances
    let [toothpaste,brush] = ['toothpaste','brush'].map(
      name => new Item({ name })
    );

    let [billsStore,tedsStore] = ['Bills','Teds'].map(
      name => new Store({ name })
    );

    // Add items to stores
    [billsStore,tedsStore].forEach( store => {
      store.items.push(toothpaste);   // add toothpaste to store
      toothpaste.stores.push(store);  // add store to toothpaste
    });

    // Brush is only in billsStore
    billsStore.items.push(brush);
    brush.stores.push(billsStore);

    // Save everything
    await Promise.all(
      [toothpaste,brush,billsStore,tedsStore].map( m => m.save() )
    );

    // Show stores
    let stores = await Store.find().populate('items','-stores');
    log(stores);

    // Show items
    let items = await Item.find().populate('stores','-items');
    log(items);

  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})();

这会创建&#34;项目&#34;系列:

{
    "_id" : ObjectId("59ab96d9c079220dd8eec428"),
    "name" : "toothpaste",
    "stores" : [
            ObjectId("59ab96d9c079220dd8eec42a"),
            ObjectId("59ab96d9c079220dd8eec42b")
    ],
    "__v" : 0
}
{
    "_id" : ObjectId("59ab96d9c079220dd8eec429"),
    "name" : "brush",
    "stores" : [
            ObjectId("59ab96d9c079220dd8eec42a")
    ],
    "__v" : 0
}

&#34; store&#34;系列:

{
    "_id" : ObjectId("59ab96d9c079220dd8eec42a"),
    "name" : "Bills",
    "items" : [
            ObjectId("59ab96d9c079220dd8eec428"),
            ObjectId("59ab96d9c079220dd8eec429")
    ],
    "__v" : 0
}
{
    "_id" : ObjectId("59ab96d9c079220dd8eec42b"),
    "name" : "Teds",
    "items" : [
            ObjectId("59ab96d9c079220dd8eec428")
    ],
    "__v" : 0
}

并产生如下的整体输出:

Mongoose: items.deleteMany({}, {})
Mongoose: stores.deleteMany({}, {})
Mongoose: items.insertOne({ name: 'toothpaste', _id: ObjectId("59ab96d9c079220dd8eec428"), stores: [ ObjectId("59ab96d9c079220dd8eec42a"), ObjectId("59ab96d9c079220dd8eec42b") ], __v: 0 })
Mongoose: items.insertOne({ name: 'brush', _id: ObjectId("59ab96d9c079220dd8eec429"), stores: [ ObjectId("59ab96d9c079220dd8eec42a") ], __v: 0 })
Mongoose: stores.insertOne({ name: 'Bills', _id: ObjectId("59ab96d9c079220dd8eec42a"), items: [ ObjectId("59ab96d9c079220dd8eec428"), ObjectId("59ab96d9c079220dd8eec429") ], __v: 0 })
Mongoose: stores.insertOne({ name: 'Teds', _id: ObjectId("59ab96d9c079220dd8eec42b"), items: [ ObjectId("59ab96d9c079220dd8eec428") ], __v: 0 })
Mongoose: stores.find({}, { fields: {} })
Mongoose: items.find({ _id: { '$in': [ ObjectId("59ab96d9c079220dd8eec428"), ObjectId("59ab96d9c079220dd8eec429") ] } }, { fields: { stores: 0 } })
[
  {
    "_id": "59ab96d9c079220dd8eec42a",
    "name": "Bills",
    "__v": 0,
    "items": [
      {
        "_id": "59ab96d9c079220dd8eec428",
        "name": "toothpaste",
        "__v": 0
      },
      {
        "_id": "59ab96d9c079220dd8eec429",
        "name": "brush",
        "__v": 0
      }
    ]
  },
  {
    "_id": "59ab96d9c079220dd8eec42b",
    "name": "Teds",
    "__v": 0,
    "items": [
      {
        "_id": "59ab96d9c079220dd8eec428",
        "name": "toothpaste",
        "__v": 0
      }
    ]
  }
]
Mongoose: items.find({}, { fields: {} })
Mongoose: stores.find({ _id: { '$in': [ ObjectId("59ab96d9c079220dd8eec42a"), ObjectId("59ab96d9c079220dd8eec42b") ] } }, { fields: { items: 0 } })
[
  {
    "_id": "59ab96d9c079220dd8eec428",
    "name": "toothpaste",
    "__v": 0,
    "stores": [
      {
        "_id": "59ab96d9c079220dd8eec42a",
        "name": "Bills",
        "__v": 0
      },
      {
        "_id": "59ab96d9c079220dd8eec42b",
        "name": "Teds",
        "__v": 0
      }
    ]
  },
  {
    "_id": "59ab96d9c079220dd8eec429",
    "name": "brush",
    "__v": 0,
    "stores": [
      {
        "_id": "59ab96d9c079220dd8eec42a",
        "name": "Bills",
        "__v": 0
      }
    ]
  }
]

关键点在于您实际将参考数据添加到存在关系的每个集合中的每个文档。 &#34;阵列&#34; present用于存储这些引用和&#34; lookup&#34;来自相关集合的结果,并将其替换为存储在那里的对象数据。

注意以下部分:

// Add items to stores
[billsStore,tedsStore].forEach( store => {
  store.items.push(toothpaste);   // add toothpaste to store
  toothpaste.stores.push(store);  // add store to toothpaste
});

因为这意味着我们不仅要将toothpaste添加到每个商店的"items"数组中,而且还要将每个"store"添加到"stores"数组中。 toothpaste项。这样做可以从任一方向查询关系。如果你只想要来自商店的商品&#34;并且从不&#34;存储来自商品&#34;,然后您不需要将关系数据存储在&#34;项目&#34;条目。

清单2 - 使用虚拟和中介收集

这基本上是经典的&#34;多对多&#34;关系。而不是直接定义两个集合之间的关系,而是有另一个集合(表)存储有关哪个项与哪个商店相关的详细信息。

作为完整列表:

const { Schema } = mongoose = require('mongoose');

mongoose.Promise = global.Promise;
mongoose.set('debug',true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

const uri = 'mongodb://localhost:27017/manydemo',
      options = { useNewUrlParser: true };

const itemSchema = new Schema({
  name: String,
},{
 toJSON: { virtuals: true }
});

itemSchema.virtual('stores', {
  ref: 'StoreItem',
  localField: '_id',
  foreignField: 'itemId'
});

const storeSchema = new Schema({
  name: String,
},{
 toJSON: { virtuals: true }
});

storeSchema.virtual('items', {
  ref: 'StoreItem',
  localField: '_id',
  foreignField: 'storeId'
});

const storeItemSchema = new Schema({
  storeId: { type: Schema.Types.ObjectId, ref: 'Store', required: true },
  itemId: { type: Schema.Types.ObjectId, ref: 'Item', required: true }
});

const Item = mongoose.model('Item', itemSchema);
const Store = mongoose.model('Store', storeSchema);
const StoreItem = mongoose.model('StoreItem', storeItemSchema);

const log = data => console.log(JSON.stringify(data,undefined,2));

(async function() {

  try {

    const conn = await mongoose.connect(uri,options);

    // Clean data
    await Promise.all(
      Object.entries(conn.models).map(([k,m]) => m.deleteMany() )
    );

    // Create some instances
    let [toothpaste,brush] = await Item.insertMany(
      ['toothpaste','brush'].map( name => ({ name }) )
    );
    let [billsStore,tedsStore] = await Store.insertMany(
      ['Bills','Teds'].map( name => ({ name }) )
    );

    // Add toothpaste to both stores
    for( let store of [billsStore,tedsStore] ) {
      await StoreItem.update(
        { storeId: store._id, itemId: toothpaste._id },
        { },
        { 'upsert': true }
      );
    }

    // Add brush to billsStore
    await StoreItem.update(
      { storeId: billsStore._id, itemId: brush._id },
      {},
      { 'upsert': true }
    );

    // Show stores
    let stores = await Store.find().populate({
      path: 'items',
      populate: { path: 'itemId' }
    });
    log(stores);

    // Show Items
    let items = await Item.find().populate({
      path: 'stores',
      populate: { path: 'storeId' }
    });
    log(items);


  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})();

关系现在在他们自己的集合中,所以现在数据显示不同,对于&#34; items&#34;:

{
    "_id" : ObjectId("59ab996166d5cc0e0d164d74"),
    "__v" : 0,
    "name" : "toothpaste"
}
{
    "_id" : ObjectId("59ab996166d5cc0e0d164d75"),
    "__v" : 0,
    "name" : "brush"
}

&#34; store&#34;:

{
    "_id" : ObjectId("59ab996166d5cc0e0d164d76"),
    "__v" : 0,
    "name" : "Bills"
}
{
    "_id" : ObjectId("59ab996166d5cc0e0d164d77"),
    "__v" : 0,
    "name" : "Teds"
}

现在为&#34;体育馆&#34;它映射了关系:

{
    "_id" : ObjectId("59ab996179e41cc54405b72b"),
    "itemId" : ObjectId("59ab996166d5cc0e0d164d74"),
    "storeId" : ObjectId("59ab996166d5cc0e0d164d76"),
    "__v" : 0
}
{
    "_id" : ObjectId("59ab996179e41cc54405b72d"),
    "itemId" : ObjectId("59ab996166d5cc0e0d164d74"),
    "storeId" : ObjectId("59ab996166d5cc0e0d164d77"),
    "__v" : 0
}
{
    "_id" : ObjectId("59ab996179e41cc54405b72f"),
    "itemId" : ObjectId("59ab996166d5cc0e0d164d75"),
    "storeId" : ObjectId("59ab996166d5cc0e0d164d76"),
    "__v" : 0
}

完整输出如:

Mongoose: items.deleteMany({}, {})
Mongoose: stores.deleteMany({}, {})
Mongoose: storeitems.deleteMany({}, {})
Mongoose: items.insertMany([ { __v: 0, name: 'toothpaste', _id: 59ab996166d5cc0e0d164d74 }, { __v: 0, name: 'brush', _id: 59ab996166d5cc0e0d164d75 } ])
Mongoose: stores.insertMany([ { __v: 0, name: 'Bills', _id: 59ab996166d5cc0e0d164d76 }, { __v: 0, name: 'Teds', _id: 59ab996166d5cc0e0d164d77 } ])
Mongoose: storeitems.update({ itemId: ObjectId("59ab996166d5cc0e0d164d74"), storeId: ObjectId("59ab996166d5cc0e0d164d76") }, { '$setOnInsert': { __v: 0 } }, { upsert: true })
Mongoose: storeitems.update({ itemId: ObjectId("59ab996166d5cc0e0d164d74"), storeId: ObjectId("59ab996166d5cc0e0d164d77") }, { '$setOnInsert': { __v: 0 } }, { upsert: true })
Mongoose: storeitems.update({ itemId: ObjectId("59ab996166d5cc0e0d164d75"), storeId: ObjectId("59ab996166d5cc0e0d164d76") }, { '$setOnInsert': { __v: 0 } }, { upsert: true })
Mongoose: stores.find({}, { fields: {} })
Mongoose: storeitems.find({ storeId: { '$in': [ ObjectId("59ab996166d5cc0e0d164d76"), ObjectId("59ab996166d5cc0e0d164d77") ] } }, { fields: {} })
Mongoose: items.find({ _id: { '$in': [ ObjectId("59ab996166d5cc0e0d164d74"), ObjectId("59ab996166d5cc0e0d164d75") ] } }, { fields: {} })
[
  {
    "_id": "59ab996166d5cc0e0d164d76",
    "__v": 0,
    "name": "Bills",
    "items": [
      {
        "_id": "59ab996179e41cc54405b72b",
        "itemId": {
          "_id": "59ab996166d5cc0e0d164d74",
          "__v": 0,
          "name": "toothpaste",
          "stores": null,
          "id": "59ab996166d5cc0e0d164d74"
        },
        "storeId": "59ab996166d5cc0e0d164d76",
        "__v": 0
      },
      {
        "_id": "59ab996179e41cc54405b72f",
        "itemId": {
          "_id": "59ab996166d5cc0e0d164d75",
          "__v": 0,
          "name": "brush",
          "stores": null,
          "id": "59ab996166d5cc0e0d164d75"
        },
        "storeId": "59ab996166d5cc0e0d164d76",
        "__v": 0
      }
    ],
    "id": "59ab996166d5cc0e0d164d76"
  },
  {
    "_id": "59ab996166d5cc0e0d164d77",
    "__v": 0,
    "name": "Teds",
    "items": [
      {
        "_id": "59ab996179e41cc54405b72d",
        "itemId": {
          "_id": "59ab996166d5cc0e0d164d74",
          "__v": 0,
          "name": "toothpaste",
          "stores": null,
          "id": "59ab996166d5cc0e0d164d74"
        },
        "storeId": "59ab996166d5cc0e0d164d77",
        "__v": 0
      }
    ],
    "id": "59ab996166d5cc0e0d164d77"
  }
]
Mongoose: items.find({}, { fields: {} })
Mongoose: storeitems.find({ itemId: { '$in': [ ObjectId("59ab996166d5cc0e0d164d74"), ObjectId("59ab996166d5cc0e0d164d75") ] } }, { fields: {} })
Mongoose: stores.find({ _id: { '$in': [ ObjectId("59ab996166d5cc0e0d164d76"), ObjectId("59ab996166d5cc0e0d164d77") ] } }, { fields: {} })
[
  {
    "_id": "59ab996166d5cc0e0d164d74",
    "__v": 0,
    "name": "toothpaste",
    "stores": [
      {
        "_id": "59ab996179e41cc54405b72b",
        "itemId": "59ab996166d5cc0e0d164d74",
        "storeId": {
          "_id": "59ab996166d5cc0e0d164d76",
          "__v": 0,
          "name": "Bills",
          "items": null,
          "id": "59ab996166d5cc0e0d164d76"
        },
        "__v": 0
      },
      {
        "_id": "59ab996179e41cc54405b72d",
        "itemId": "59ab996166d5cc0e0d164d74",
        "storeId": {
          "_id": "59ab996166d5cc0e0d164d77",
          "__v": 0,
          "name": "Teds",
          "items": null,
          "id": "59ab996166d5cc0e0d164d77"
        },
        "__v": 0
      }
    ],
    "id": "59ab996166d5cc0e0d164d74"
  },
  {
    "_id": "59ab996166d5cc0e0d164d75",
    "__v": 0,
    "name": "brush",
    "stores": [
      {
        "_id": "59ab996179e41cc54405b72f",
        "itemId": "59ab996166d5cc0e0d164d75",
        "storeId": {
          "_id": "59ab996166d5cc0e0d164d76",
          "__v": 0,
          "name": "Bills",
          "items": null,
          "id": "59ab996166d5cc0e0d164d76"
        },
        "__v": 0
      }
    ],
    "id": "59ab996166d5cc0e0d164d75"
  }
]

由于关系现在映射到一个单独的集合中,因此这里有一些更改。值得注意的是,我们想要定义一个&#34;虚拟&#34;集合上不再具有固定项目数组的字段。所以你添加一个如下所示:

const itemSchema = new Schema({
  name: String,
},{
 toJSON: { virtuals: true }
});

itemSchema.virtual('stores', {
  ref: 'StoreItem',
  localField: '_id',
  foreignField: 'itemId'
});

您可以为虚拟字段分配localFieldforeignField映射,以便后续.populate()调用知道要使用的内容。

中间集合有一个相当标准的定义:

const storeItemSchema = new Schema({
  storeId: { type: Schema.Types.ObjectId, ref: 'Store', required: true },
  itemId: { type: Schema.Types.ObjectId, ref: 'Item', required: true }
});

而不是&#34;推动&#34;将新项目添加到数组中,我们将它们添加到此新集合中。一个合理的方法是使用&#34; upserts&#34;仅当此组合不存在时才创建新条目:

// Add toothpaste to both stores
for( let store of [billsStore,tedsStore] ) {
  await StoreItem.update(
    { storeId: store._id, itemId: toothpaste._id },
    { },
    { 'upsert': true }
  );
}

这是一个非常简单的方法,只是创建一个新文档,其中包含查询中提供的两个键,其中一个未找到,或者基本上尝试在匹配时更新同一个文档,并且&#34;没有&# 34;在这种情况下。所以现有的比赛最终只是作为一个&#34; no-op&#34;,这是最理想的事情。或者,您可以简单地.insertOne()忽略重复键错误。无论你喜欢什么。

实际上查询这个&#34;相关&#34;数据再次有所不同。因为涉及另一个集合,我们以一种认为需要&#34;查找&#34;的方式调用.populate()。其他检索财产的关系也是如此。所以你有这样的电话:

 // Show stores
  let stores = await Store.find().populate({
    path: 'items',
    populate: { path: 'itemId' }
  });
  log(stores);

清单3 - 使用现代功能在服务器上执行

因此,取决于采用哪种方法,使用数组或中间集合来存储关系数据,以替代&#34;增长数组&#34;在文档中,你应该注意到的显而易见的事情是,所使用的.populate()调用实际上是对MongoDB进行额外的查询,并在单独的请求中通过网络提取这些文档。

这可能在小剂量下看起来都很好,但随着事物的扩大,特别是在请求量上,这绝不是一件好事。此外,您可能希望应用其他条件,这意味着您不需要从服务器提取所有文档,而是希望匹配来自这些&#34;关系的数据。在你返回结果之前。

这就是为什么现代MongoDB版本包括$lookup实际上#34;加入&#34;服务器本身的数据。到目前为止,您应该查看这些API调用生成的所有输出,如mongoose.set('debug',true)所示。

因此,我们不是生成多个查询,而是将其作为一个聚合语句来加入&#34; join&#34;在服务器上,并在一个请求中返回结果:

// Show Stores
let stores = await Store.aggregate([
  { '$lookup': {
    'from': StoreItem.collection.name,
    'let': { 'id': '$_id' },
    'pipeline': [
      { '$match': {
        '$expr': { '$eq': [ '$$id', '$storeId' ] }
      }},
      { '$lookup': {
        'from': Item.collection.name,
        'let': { 'itemId': '$itemId' },
        'pipeline': [
          { '$match': {
            '$expr': { '$eq': [ '$_id', '$$itemId' ] }
          }}
        ],
        'as': 'items'
      }},
      { '$unwind': '$items' },
      { '$replaceRoot': { 'newRoot': '$items' } }
    ],
    'as': 'items'
  }}
])
log(stores);

虽然在编码时间较长,但实际上效率甚至超过了这里非常微不足道的行动。这当然会大大扩展。

遵循相同的&#34;中介&#34;模型和以前一样(例如,因为它可以以任何一种方式完成)我们有一个完整的列表:

const { Schema } = mongoose = require('mongoose');

const uri = 'mongodb://localhost:27017/manydemo',
      options = { useNewUrlParser: true };

mongoose.Promise = global.Promise;
mongoose.set('debug', true);
mongoose.set('useFindAndModify', false);
mongoose.set('useCreateIndex', true);

const itemSchema = new Schema({
  name: String
}, {
  toJSON: { virtuals: true }
});

itemSchema.virtual('stores', {
  ref: 'StoreItem',
  localField: '_id',
  foreignField: 'itemId'
});

const storeSchema = new Schema({
  name: String
}, {
  toJSON: { virtuals: true }
});

storeSchema.virtual('items', {
  ref: 'StoreItem',
  localField: '_id',
  foreignField: 'storeId'
});

const storeItemSchema = new Schema({
  storeId: { type: Schema.Types.ObjectId, ref: 'Store', required: true },
  itemId: { type: Schema.Types.ObjectId, ref: 'Item', required: true }
});

const Item = mongoose.model('Item', itemSchema);
const Store = mongoose.model('Store', storeSchema);
const StoreItem = mongoose.model('StoreItem', storeItemSchema);

const log = data => console.log(JSON.stringify(data, undefined, 2));

(async function() {

  try {

    const conn = await mongoose.connect(uri, options);

    // Clean data
    await Promise.all(
      Object.entries(conn.models).map(([k,m]) => m.deleteMany())
    );

    // Create some instances
    let [toothpaste, brush] = await Item.insertMany(
      ['toothpaste', 'brush'].map(name => ({ name }) )
    );
    let [billsStore, tedsStore] = await Store.insertMany(
      ['Bills', 'Teds'].map( name => ({ name }) )
    );

    // Add toothpaste to both stores
    for ( let { _id: storeId }  of [billsStore, tedsStore] ) {
      await StoreItem.updateOne(
        { storeId, itemId: toothpaste._id },
        { },
        { 'upsert': true }
      );
    }

    // Add brush to billsStore
    await StoreItem.updateOne(
      { storeId: billsStore._id, itemId: brush._id },
      { },
      { 'upsert': true }
    );

    // Show Stores
    let stores = await Store.aggregate([
      { '$lookup': {
        'from': StoreItem.collection.name,
        'let': { 'id': '$_id' },
        'pipeline': [
          { '$match': {
            '$expr': { '$eq': [ '$$id', '$storeId' ] }
          }},
          { '$lookup': {
            'from': Item.collection.name,
            'let': { 'itemId': '$itemId' },
            'pipeline': [
              { '$match': {
                '$expr': { '$eq': [ '$_id', '$$itemId' ] }
              }}
            ],
            'as': 'items'
          }},
          { '$unwind': '$items' },
          { '$replaceRoot': { 'newRoot': '$items' } }
        ],
        'as': 'items'
      }}
    ])

    log(stores);

    // Show Items
    let items = await Item.aggregate([
      { '$lookup': {
        'from': StoreItem.collection.name,
        'let': { 'id': '$_id' },
        'pipeline': [
          { '$match': {
            '$expr': { '$eq': [ '$$id', '$itemId' ] }
          }},
          { '$lookup': {
            'from': Store.collection.name,
            'let': { 'storeId': '$storeId' },
            'pipeline': [
              { '$match': {
                '$expr': { '$eq': [ '$_id', '$$storeId' ] }
              }}
            ],
            'as': 'stores',
          }},
          { '$unwind': '$stores' },
          { '$replaceRoot': { 'newRoot': '$stores' } }
        ],
        'as': 'stores'
      }}
    ]);

    log(items);


  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})()

输出:

Mongoose: stores.aggregate([ { '$lookup': { from: 'storeitems', let: { id: '$_id' }, pipeline: [ { '$match': { '$expr': { '$eq': [ '$$id', '$storeId' ] } } }, { '$lookup': { from: 'items', let: { itemId: '$itemId' }, pipeline: [ { '$match': { '$expr': { '$eq': [ '$_id', '$$itemId' ] } } } ], as: 'items' } }, { '$unwind': '$items' }, { '$replaceRoot': { newRoot: '$items' } } ], as: 'items' } } ], {})
[
  {
    "_id": "5ca7210717dadc69652b37da",
    "name": "Bills",
    "__v": 0,
    "items": [
      {
        "_id": "5ca7210717dadc69652b37d8",
        "name": "toothpaste",
        "__v": 0
      },
      {
        "_id": "5ca7210717dadc69652b37d9",
        "name": "brush",
        "__v": 0
      }
    ]
  },
  {
    "_id": "5ca7210717dadc69652b37db",
    "name": "Teds",
    "__v": 0,
    "items": [
      {
        "_id": "5ca7210717dadc69652b37d8",
        "name": "toothpaste",
        "__v": 0
      }
    ]
  }
]
Mongoose: items.aggregate([ { '$lookup': { from: 'storeitems', let: { id: '$_id' }, pipeline: [ { '$match': { '$expr': { '$eq': [ '$$id', '$itemId' ] } } }, { '$lookup': { from: 'stores', let: { storeId: '$storeId' }, pipeline: [ { '$match': { '$expr': { '$eq': [ '$_id', '$$storeId' ] } } } ], as: 'stores' } }, { '$unwind': '$stores' }, { '$replaceRoot': { newRoot: '$stores' } } ], as: 'stores' } } ], {})
[
  {
    "_id": "5ca7210717dadc69652b37d8",
    "name": "toothpaste",
    "__v": 0,
    "stores": [
      {
        "_id": "5ca7210717dadc69652b37da",
        "name": "Bills",
        "__v": 0
      },
      {
        "_id": "5ca7210717dadc69652b37db",
        "name": "Teds",
        "__v": 0
      }
    ]
  },
  {
    "_id": "5ca7210717dadc69652b37d9",
    "name": "brush",
    "__v": 0,
    "stores": [
      {
        "_id": "5ca7210717dadc69652b37da",
        "name": "Bills",
        "__v": 0
      }
    ]
  }
]

显而易见的是,最终发出的查询显着减少了返回&#34;加入&#34;数据的形式。这意味着通过消除所有网络开销,可以降低延迟和响应速度更快的应用程序。

最终笔记

这些通常是您处理&#34;多对多的方法&#34;关系,基本上归结为:

  • 在每一个文档中保留数组,保留对相关项的引用。

  • 存储中间集合并将其用作查找引用以检索其他数据。

在所有情况下,如果您希望事情能够在两个方向上发挥作用,那么实际存储这些引用取决于您 。当然$lookup甚至&#34;虚拟&#34;适用的地方意味着您并不总是需要存储在每个来源上,因为您可以随时参考&#34;只在一个地方使用这些方法来使用这些信息。

另一种情况当然是&#34;嵌入&#34;,这是一个完全不同的游戏,以及像MongoDB这样的面向文档的数据库实际上是关于什么的。因此,而不是从另一个集合中取出&#34;这个概念当然是&#34;嵌入&#34;数据。

这不仅意味着指向其他项的ObjectId值,而且实际上将完整数据存储在每个文档的数组中。当然存在&#34;尺寸&#34;当然还有在多个地方更新数据的问题。这通常是存在单一请求简单请求的权衡,因为它不需要去其他集合中找到数据,因为它&#39;&# 34;已经在那里&#34;。

关于引用和嵌入的主题有很多材料。一旦此类摘要来源为Mongoose populate vs object nesting,或者甚至是非常笼统的MongoDB relationships: embed or reference?以及许多其他摘要来源。

您应该花一些时间考虑这些概念以及它如何适用于您的应用程序。请注意,您实际上并没有在这里使用RDBMS,所以您也可以使用您想要利用的正确功能,而不是简单地使用另一个行为。

答案 1 :(得分:0)

在建模数据库之前,首先应考虑应用程序中数据的使用。

我没有您的申请的详细要求。但为什么你必须在2个模式中保留2个引用?为什么不保持从StoreItem的1个引用(这意味着1个商店有很多项),然后如果你想执行查询来查找项目属于哪个商店,那么也有通过查询Store集合来实现。

此外,MongoDB中没有任何称为“多对多”的内容。这取决于数据的使用方式,您必须找出形成集合之间关系的有效方法,以及构建数据库。

无论如何,如果你仍想使用当前的模式,你可以先创建项目,然后创建商店并将创建的项目的id推送到items数组,然后对创建的项目执行更新商店的id。