当引用

时间:2017-07-09 09:46:35

标签: javascript node.js mongodb mongodb-query aggregation-framework

我正在尝试使用Mongoose的Model.populate()将用户ID转换为用户,在通过聚合和展开获取的文档的子结构中。我猜测我的架构有问题或者可能解开是打破了与子架构的连接。

问题是:当存在有效引用时,不会调用填充回调。如果没有子结构,则调用回调,原始文档不变。

结构:

我有文章,用户可以分别没有或多个ArticleRatings。

我在ArticleRating中使用了两个引用,与文章和评分用户相关。

流程:

该过程实际上是将文章导出为(旧)CSV格式并将结构展平为具有用户特定评级的重复文章行。展开非常适合此操作,并且保留空值可以保留没有评级的文章。

调试:

我已经尝试深入了解Model.populate代码。所有的promises和回调包装器都变得非常复杂,但我可以看到底层的填充调用也没有调用内部回调。我没有使用承诺变种 - 但不是100%肯定我是否应该? (Mongoose docs对回调和承诺之间的用例有点模糊)。

我已经仔细检查了我的架构,尝试将模型显式添加到填充调用中(因为它不需要在架构中)。没有错误或例外,它不会崩溃。

逐步浏览Chrome调试器中的代码就像我期望的那样显示模型:前几篇文章的rating.userId具有有效的ObjectId,但在这种情况下,填充回调根本就没有被叫。接下来的一百篇文章没有"等级" set,并且可以为所有这些调用可靠地调用回调。

所以我猜测我做错了什么导致Model.populate走上一条没有正确错误的路径?

注意:我知道我可以重写代码以使用聚合$ lookup或其他嵌入结构而不是外部引用,但我是在功能拼图的最后一部分,并希望将其作为 - 是

这是简化的架构:

const ArticleRatingSchema = new Schema({
    articleId: {type: Schema.Types.ObjectId, ref:'Article'},
    userId: {type: Schema.Types.ObjectId, ref:'User'},                          
    rating: String,                                                                                                             
    comment: String,                                                                                                        
});

const ArticleSchema = new Schema({
    title: String,
    rating: ArticleRatingSchema,
});

这是查找

    // Find all articles relating to this project, and their ratings.
    // Unwind does the duplicate per-user, and preserve keeps un-rated articles.
    articleModel.aggregate([
            {$match: {projectId: projectId}},
            {$lookup:{from:'articleratings', localField:'_id', foreignField:'articleId', as:'rating' }},
            {$unwind: {path:'$rating', preserveNullAndEmptyArrays:true}}
        ], (err, models) =>
    {
        if (!err) {

            models.map((article) => {

                articleModel.populate(article, {path:'rating.userId', model:'User'}, (err, article)=> {
                    // Process the article...
                    // this callback only gets called where there is NO rating in the article.
                });

            });
        }

2 个答案:

答案 0 :(得分:0)

我意识到这是因为我在同步map()循环中处理集合,并且两种情况必须不同,因为不匹配的populate是同步回调,而匹配的替换稍后回调。

如果我在回调中使用console.log(),我发现在已经格式化和下载CSV之后,最后处理了四个匹配的案例。

所以答案是:填充IS被调用,但异步。

我需要重新设计map()循环以适应通常的异步模式。

答案 1 :(得分:0)

我个人对您认为在聚合管道中使用$lookup然后想要.populate()结果感到困惑。因为要求使用.populate()实质上意味着正在向服务器发出其他查询,以便模拟联接"。

因此,$lookup实际上是"加入服务器"那么你真的应该只使用$lookup

您可以使用.populate(),我会显示一些代码,以表明可以完成。但这里真的很多余,因为你可以在服务器上完成所有的工作。

所以我最好的"近似"您似乎拥有的结构是:

<强>制品

{
        "_id" : ObjectId("5962104312246235cdcceb16"),
        "title" : "New News",
        "ratings" : [ ],
        "__v" : 0
}

<强> articleratings

{
        "_id" : ObjectId("5962104312246235cdcceb17"),
        "articleId" : ObjectId("5962104312246235cdcceb16"),
        "userId" : ObjectId("5962104312246235cdcceb13"),
        "rating" : "5",
        "comment" : "Great!",
        "__v" : 0
}
{
        "_id" : ObjectId("5962104312246235cdcceb18"),
        "articleId" : ObjectId("5962104312246235cdcceb16"),
        "userId" : ObjectId("5962104312246235cdcceb14"),
        "rating" : "3",
        "comment" : "Okay I guess ;)",
        "__v" : 0
}
{
        "_id" : ObjectId("5962104312246235cdcceb19"),
        "articleId" : ObjectId("5962104312246235cdcceb16"),
        "userId" : ObjectId("5962104312246235cdcceb15"),
        "rating" : "1",
        "comment" : "Hated it :<",
        "__v" : 0
}

用户

{
        "_id" : ObjectId("5962104312246235cdcceb13"),
        "name" : "Bill",
        "email" : "bill@example.com",
        "__v" : 0
}
{
        "_id" : ObjectId("5962104312246235cdcceb14"),
        "name" : "Fred",
        "email" : "fred@example.com",
        "__v" : 0
}
{
        "_id" : ObjectId("5962104312246235cdcceb15"),
        "name" : "Ted",
        "email" : "ted@example.com",
        "__v" : 0
}

然后是汇总声明:

  Article.aggregate(
    [
      { "$lookup": {
        "from": ArticleRating.collection.name,
        "localField": "_id",
        "foreignField": "articleId",
        "as": "ratings"
      }},
      { "$unwind": "$ratings" },
      { "$lookup": {
        "from": User.collection.name,
        "localField": "ratings.userId",
        "foreignField": "_id",
        "as": "ratings.userId",
      }},
      { "$unwind": "$ratings.userId" },
      { "$group": {
        "_id": "$_id",
        "title": { "$first": "$title" },
        "ratings": { "$push": "$ratings" }
      }}
    ],
    (err,articles) => {
      if (err) callback(err);
      log(articles);
      callback();
    }
  )

结果:

  {
    "_id": "5962126f3ef2fb35efeefd94",
    "title": "New News",
    "ratings": [
      {
        "_id": "5962126f3ef2fb35efeefd95",
        "articleId": "5962126f3ef2fb35efeefd94",
        "userId": {
          "_id": "5962126f3ef2fb35efeefd91",
          "name": "Bill",
          "email": "bill@example.com",
          "__v": 0
        },
        "rating": "5",
        "comment": "Great!",
        "__v": 0
      },
      {
        "_id": "5962126f3ef2fb35efeefd96",
        "articleId": "5962126f3ef2fb35efeefd94",
        "userId": {
          "_id": "5962126f3ef2fb35efeefd92",
          "name": "Fred",
          "email": "fred@example.com",
          "__v": 0
        },
        "rating": "3",
        "comment": "Okay I guess ;)",
        "__v": 0
      },
      {
        "_id": "5962126f3ef2fb35efeefd97",
        "articleId": "5962126f3ef2fb35efeefd94",
        "userId": {
          "_id": "5962126f3ef2fb35efeefd93",
          "name": "Ted",
          "email": "ted@example.com",
          "__v": 0
        },
        "rating": "1",
        "comment": "Hated it :<",
        "__v": 0
      }
    ]
  }

对于&#34;填充&#34;没有意义的地方引用&#34; articleId&#34;关于&#34;评级&#34;他们自己。但我们确实已经填充了#34; &#34;评级&#34;到文章和&#34;用户&#34;对于每个评级。

示例清单

使用.populate()(在$lookup之后)和您正在尝试的方式同时显示两种方式,并且只使用普通$lookup

方法同时使用&#34;普通承诺&#34;并与async.map交替使用:

const async = require('async'),
      mongoose = require('mongoose'),
      Schema = mongoose.Schema;

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

mongoose.connect('mongodb://localhost/publication');

const userSchema = new Schema({
  name: String,
  email: String
});

const User = mongoose.model('User', userSchema);

const articleRatingSchema = new Schema({
  articleId: {type: Schema.Types.ObjectId, ref:'Article'},
  userId: {type: Schema.Types.ObjectId, ref:'User'},
  rating: String,
  comment: String,
});

const articleSchema = new Schema({
  title: String,
  ratings: [articleRatingSchema]
})

const Article = mongoose.model('Article', articleSchema);
const ArticleRating = mongoose.model('ArticleRating', articleRatingSchema);

function log(data) {
  console.log(JSON.stringify(data,undefined,2))
}


const userData = [
  { name: 'Bill', rating: 5, comment: 'Great!' },
  { name: 'Fred', rating: 3, comment: 'Okay I guess ;)' },
  { name: 'Ted',  rating: 1, comment: 'Hated it :<' }
];

async.series(
  [
    // Clean data
    (callback) =>
      async.each(mongoose.models,(model,callback) =>
        model.remove({},callback),callback),

    // Insert data
    (callback) =>
      async.waterfall(
        [
          // User and article
          (callback) =>
            async.parallel(
              {
                "users": (callback) =>
                  User.create(
                    ["Bill", "Fred", "Ted"].map( name =>
                      ({ name, email: `${name.toLowerCase()}@example.com` })
                    ),
                    callback
                  ),

                "article": (callback) =>
                  Article.create({ title: "New News" },callback)
              },
              callback
            ),

          // Article Ratings
          (data,callback) =>
            ArticleRating.create(
              data.users.map( u => ({
                articleId: data.article._id,
                userId: u._id,
                rating: userData.find( ud => ud.name === u.name ).rating,
                comment: userData.find( ud => ud.name === u.name ).comment
              })),
              callback
            )
        ],
        callback
      ),

    // $lookup and populate async.map
    (callback) =>
      Article.aggregate(
        [
          { "$lookup": {
            "from": ArticleRating.collection.name,
            "localField": "_id",
            "foreignField": "articleId",
            "as": "ratings"
          }}
        ],
        (err,articles) => {
          if (err) callback(err);
          async.map(
            articles.map( a => new Article(a) ),
            (article,callback) =>
              async.map(
                article.ratings,
                (rating,callback) =>
                  ArticleRating.populate(rating,{ path: 'userId' },callback),
                (err,ratings) => {
                  if (err) callback(err);
                  article.ratings = ratings
                  callback(null,article)
                }
              ),
            (err,articles) => {
              if (err) callback(err);
              log(articles);
              callback();
            }
          )
        }
      ),


    // $look and populate Promise
    (callback) =>
      Article.aggregate(
        [
          { "$lookup": {
            "from": ArticleRating.collection.name,
            "localField": "_id",
            "foreignField": "articleId",
            "as": "ratings"
          }}
        ]
      )
      .then(articles =>
        Promise.all(
          articles.map( a => new Article(a) ).map(article =>
            new Promise((resolve,reject) => {
              Promise.all(
                article.ratings.map( rating =>
                  ArticleRating.populate(rating,{ path: 'userId' })
                )
              ).then(ratings => {
                article.ratings = ratings;
                resolve(article);
              }).catch(reject)
            })
          )
        )
      )
      .then(articles => {
        log(articles);
        callback();
      })
      .catch(err => callback(err)),


    // Plain $lookup
    (callback) =>
      Article.aggregate(
        [
          { "$lookup": {
            "from": ArticleRating.collection.name,
            "localField": "_id",
            "foreignField": "articleId",
            "as": "ratings"
          }},
          { "$unwind": "$ratings" },
          { "$lookup": {
            "from": User.collection.name,
            "localField": "ratings.userId",
            "foreignField": "_id",
            "as": "ratings.userId",
          }},
          { "$unwind": "$ratings.userId" },
          { "$group": {
            "_id": "$_id",
            "title": { "$first": "$title" },
            "ratings": { "$push": "$ratings" }
          }}
        ],
        (err,articles) => {
          if (err) callback(err);
          log(articles);
          callback();
        }
      )
  ],
  (err) => {
    if (err) throw err;
    mongoose.disconnect();
  }
);

完整输出

Mongoose: users.remove({}, {})
Mongoose: articles.remove({}, {})
Mongoose: articleratings.remove({}, {})
Mongoose: users.insert({ name: 'Bill', email: 'bill@example.com', _id: ObjectId("596219ff6f73ed36d868ed40"), __v: 0 })
Mongoose: users.insert({ name: 'Fred', email: 'fred@example.com', _id: ObjectId("596219ff6f73ed36d868ed41"), __v: 0 })
Mongoose: users.insert({ name: 'Ted', email: 'ted@example.com', _id: ObjectId("596219ff6f73ed36d868ed42"), __v: 0 })
Mongoose: articles.insert({ title: 'New News', _id: ObjectId("596219ff6f73ed36d868ed43"), ratings: [], __v: 0 })
Mongoose: articleratings.insert({ articleId: ObjectId("596219ff6f73ed36d868ed43"), userId: ObjectId("596219ff6f73ed36d868ed40"), rating: '5', comment: 'Great!', _id: ObjectId("596219ff6f73ed36d868ed44"), __v: 0 })
Mongoose: articleratings.insert({ articleId: ObjectId("596219ff6f73ed36d868ed43"), userId: ObjectId("596219ff6f73ed36d868ed41"), rating: '3', comment: 'Okay I guess ;)', _id: ObjectId("596219ff6f73ed36d868ed45"), __v: 0 })
Mongoose: articleratings.insert({ articleId: ObjectId("596219ff6f73ed36d868ed43"), userId: ObjectId("596219ff6f73ed36d868ed42"), rating: '1', comment: 'Hated it :<', _id: ObjectId("596219ff6f73ed36d868ed46"), __v: 0 })
Mongoose: articles.aggregate([ { '$lookup': { from: 'articleratings', localField: '_id', foreignField: 'articleId', as: 'ratings' } } ], {})
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed40") ] } }, { fields: {} })
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed41") ] } }, { fields: {} })
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed42") ] } }, { fields: {} })
[
  {
    "_id": "596219ff6f73ed36d868ed43",
    "title": "New News",
    "__v": 0,
    "ratings": [
      {
        "_id": "596219ff6f73ed36d868ed44",
        "articleId": "596219ff6f73ed36d868ed43",
        "userId": {
          "_id": "596219ff6f73ed36d868ed40",
          "name": "Bill",
          "email": "bill@example.com",
          "__v": 0
        },
        "rating": "5",
        "comment": "Great!",
        "__v": 0
      },
      {
        "_id": "596219ff6f73ed36d868ed45",
        "articleId": "596219ff6f73ed36d868ed43",
        "userId": {
          "_id": "596219ff6f73ed36d868ed41",
          "name": "Fred",
          "email": "fred@example.com",
          "__v": 0
        },
        "rating": "3",
        "comment": "Okay I guess ;)",
        "__v": 0
      },
      {
        "_id": "596219ff6f73ed36d868ed46",
        "articleId": "596219ff6f73ed36d868ed43",
        "userId": {
          "_id": "596219ff6f73ed36d868ed42",
          "name": "Ted",
          "email": "ted@example.com",
          "__v": 0
        },
        "rating": "1",
        "comment": "Hated it :<",
        "__v": 0
      }
    ]
  }
]
Mongoose: articles.aggregate([ { '$lookup': { from: 'articleratings', localField: '_id', foreignField: 'articleId', as: 'ratings' } } ], {})
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed40") ] } }, { fields: {} })
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed41") ] } }, { fields: {} })
Mongoose: users.find({ _id: { '$in': [ ObjectId("596219ff6f73ed36d868ed42") ] } }, { fields: {} })
[
  {
    "_id": "596219ff6f73ed36d868ed43",
    "title": "New News",
    "__v": 0,
    "ratings": [
      {
        "_id": "596219ff6f73ed36d868ed44",
        "articleId": "596219ff6f73ed36d868ed43",
        "userId": {
          "_id": "596219ff6f73ed36d868ed40",
          "name": "Bill",
          "email": "bill@example.com",
          "__v": 0
        },
        "rating": "5",
        "comment": "Great!",
        "__v": 0
      },
      {
        "_id": "596219ff6f73ed36d868ed45",
        "articleId": "596219ff6f73ed36d868ed43",
        "userId": {
          "_id": "596219ff6f73ed36d868ed41",
          "name": "Fred",
          "email": "fred@example.com",
          "__v": 0
        },
        "rating": "3",
        "comment": "Okay I guess ;)",
        "__v": 0
      },
      {
        "_id": "596219ff6f73ed36d868ed46",
        "articleId": "596219ff6f73ed36d868ed43",
        "userId": {
          "_id": "596219ff6f73ed36d868ed42",
          "name": "Ted",
          "email": "ted@example.com",
          "__v": 0
        },
        "rating": "1",
        "comment": "Hated it :<",
        "__v": 0
      }
    ]
  }
]
Mongoose: articles.aggregate([ { '$lookup': { from: 'articleratings', localField: '_id', foreignField: 'articleId', as: 'ratings' } }, { '$unwind': '$ratings' }, { '$lookup': { from: 'users', localField: 'ratings.userId', foreignField: '_id', as: 'ratings.userId' } }, { '$unwind': '$ratings.userId' }, { '$group': { _id: '$_id', title: { '$first': '$title' }, ratings: { '$push': '$ratings' } } } ], {})
[
  {
    "_id": "596219ff6f73ed36d868ed43",
    "title": "New News",
    "ratings": [
      {
        "_id": "596219ff6f73ed36d868ed44",
        "articleId": "596219ff6f73ed36d868ed43",
        "userId": {
          "_id": "596219ff6f73ed36d868ed40",
          "name": "Bill",
          "email": "bill@example.com",
          "__v": 0
        },
        "rating": "5",
        "comment": "Great!",
        "__v": 0
      },
      {
        "_id": "596219ff6f73ed36d868ed45",
        "articleId": "596219ff6f73ed36d868ed43",
        "userId": {
          "_id": "596219ff6f73ed36d868ed41",
          "name": "Fred",
          "email": "fred@example.com",
          "__v": 0
        },
        "rating": "3",
        "comment": "Okay I guess ;)",
        "__v": 0
      },
      {
        "_id": "596219ff6f73ed36d868ed46",
        "articleId": "596219ff6f73ed36d868ed43",
        "userId": {
          "_id": "596219ff6f73ed36d868ed42",
          "name": "Ted",
          "email": "ted@example.com",
          "__v": 0
        },
        "rating": "1",
        "comment": "Hated it :<",
        "__v": 0
      }
    ]
  }
]