Операция присоединения мангуста

У меня есть 3 схемы, как показано ниже:

Пользователь

    var UserSchema = new Schema({

        name: String

    });

Актер

var ActorSchema = new Schema({

    name: String

});

Рейтинг

var RatingSchema = new Schema({

    actor: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Actor'
    },
    user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'Actor'
    },
    userRating: Number

});

Я хочу отправить информацию обо всех актерах во внешний интерфейс, например [актер1, актер2 ...]. Каждый актер содержит сведения об актере и «userRating», который задается пользователем, который в данный момент вошел в систему. Пользователь может давать оценки нескольким актерам, а актер может получать оценки от нескольких пользователей. Они будут храниться в таблице рейтингов.

Я написал что-то вроде этого

Actor
        .find({})  // get all actors and populate userRating into each actor
        .populate({
            path: 'userRating',
            model: 'Rating',
            match: { actor: {$eq: req.actor}, user: {$eq: req.user}},
            select: 'userRating'
        })
        .exec(function(error, actors){
            if(error)
                res.status(501).json({error: error});
            else
                res.json(actors);
        });

В итоге у меня остались только актеры. Объект-актер не содержит userRating. может кто-нибудь исправить мой запрос


person lch    schedule 17.04.2016    source источник


Ответы (1)


Это зависит от того, что вы фактически отправляете в качестве входных данных для параметров запроса здесь. Кроме того, главное, что вам нужно понять, это то, что это не «СОЕДИНЕНИЕ», а фактически отдельные запросы, отправляемые mongoose программным уровнем, поэтому существуют явные различия в обработке.

В базовом случае, когда "значения", предоставляемые в качестве параметров, на самом деле являются ObjectId значениями ссылок, тогда вы фактически просто хотите, чтобы они были непосредственно в основном "запросе", а не аргументами для действия .populate() (что на самом деле является тем местом, где "дополнительные запросы "происходят).

Кроме того, ваши «отношения / ссылки» находятся в модели Rating, поэтому вместо этого выдается ваш запрос:

Rating.find({
  "actor": req.actor,
  "user": req.user
}).populate("actor user").exec(function(err,ratings) {
    // Matched ratings by actor and user supplied
}) 

Если вместо этого ваши параметры представляют собой "name" данные каждого объекта, то, поскольку эта информация отсутствует в модели Rating до тех пор, пока она не будет заполнена, единственный способ mongoose сделать это - получить «все» из Rating объектов, а затем выполнить «заполнение» с помощью критериям "match" и, наконец, отфильтровать любые результаты, где совокупность была null из-за несоответствующих элементов:

Rating.find().populate([
    { "path": "actor", "match": { "name": req.actor } },
    { "path": "user", "match": { "name": req.user } }
]).exec(function(err,ratings) {
    // Now filter out the null results
    ratings = ratings.filter(function(rating) {
        return ( rating.actor != null && rating.user != null )
    });
    // Then work with filtered data
})

Конечно, это очень неэффективно, поскольку это операция на стороне клиента, и вы втягиваете весь Rating контент «первым». Итак, что вы на самом деле собираетесь сделать в этом случае, так это выполнить «три» операции запроса самостоятельно и получить значения ObjectId из моделей User и Actor, чтобы вместо этого применить сопоставление к модели Rating:

async.parallel(
    {
       "user": function(callback) {
           User.findOne({ "name": req.user },callback)
       },
       "actor": function(callback) {
           Actor.findOne({ "name": req.actor },callback)
       }
    },
    function(err,data) {
       // Use returned _id values in query
       Rating.find({
           "actor": data.actor._id,
           "user": data.user._id
       }).populate("actor user").exec(err,ratings) {
           // populated Rating results
       });
    }
)

Затем запросы разрешают «только» ObjectId значения, которые вам действительно нужны, а последний запрос Rating извлекает только те результаты, которые действительно соответствуют условиям, а не все и выполняет операцию «пост-фильтра».

В качестве последнего подхода, если у вас есть MongoDB 3.2, вы можете альтернативно использовать $lookup вместо выполнения операции "СОЕДИНЕНИЯ" на "сервере":

Rating.aggregate(
  [
    { "$lookup": {
      "from": "users",
      "localField": "user",
      "foreignField": "_id",
      "as": "user"
    }},
    { "$unwind": "$user" },
    { "$match": { "user.name": req.user } },
    { "$lookup": {
      "from": "actors",
      "localField": "actor",
      "foreignField": "_id",
      "as": "actor"
    }},
    { "$unwind": "actor" },
    { "$match": { "actor.name": req.actor } }
  ],
  function(err,ratings) {
      // populated on the server in one request
  }
)

С точки зрения «клиента», это всего лишь «один» запрос и ответ в отличие от того, что делает .populate(). Но на самом деле это не более чем «серверная» интерпретация «клиентской» логики, представленной ранее.

Поэтому при поиске по значениям "name" вы должны вместо этого использовать подход «три» для оптимальной производительности, поскольку версия агрегирования все еще действительно работает с гораздо большим количеством данных, чем нужно.

Конечно, «лучшая» перспектива - просто использовать для начала значения ObjectId.

Конечно, главное здесь то, что такая информация, как "userRating", принадлежит модели Rating, и поэтому вы предоставляете «запрос» во всех случаях для получения этих данных. Это не операции «JOIN», как в SQL, поэтому «сервер» не смотрит на объединенные результаты, а затем выбирает поля.

В качестве небольшого самообразования включите «отладку», чтобы увидеть, как mongoose фактически отправляет операторы серверу. Тогда вы увидите, как на самом деле применяется .populate():

mongoose.set("debug",true)
person Neil Lunn    schedule 18.04.2016