如何使用Mongoose的lookup方法获取引用集合数据?

huangapple go评论59阅读模式
英文:

How to get reference collection data in mongoose using lookup?

问题

代码示例已翻译如下:

查找:
======

    const course = await mongoose.model('Course').aggregate()
        .match({ _id: new mongoose.Types.ObjectId(id) })
        .lookup({
            from: 'User',
            foreignField: '_id',
            localField: 'userId',
            as: 'user'
        })

课程模式:
=============

    const CourseSchema = new Schema({
      title: String,
      userId: {
        type: mongoose.Types.ObjectId,
        ref: 'User'
      },
    }, { strict: false });

预期输出:
===============

    {
        "course": [
            {
                "_id": "64b000c085e9b1aec5975249",
                "title": "test",
                "userId": "64affc47d81a4bfba92b5540",
                "user": [], // 此处的数据应该存在但为空,为什么?
            }
        ]
    }

如何在上述输出中获取用户数据?

使用 populate 可行:
====================

    const course = await mongoose.model('Course')
                .find({ _id: new mongoose.Types.ObjectId(id) })
                .populate('userId').exec();

但如何使用聚合管道实现这一点?

请注意,这些翻译只包括代码和标记的内容,不包括其他内容。如果您需要进一步的帮助或有其他问题,请随时提出。

英文:

Lookup:

const course = await mongoose.model('Course').aggregate()
    .match({ _id: new mongoose.Types.ObjectId(id) })
    .lookup({
        from: 'User',
        foreignField: '_id',
        localField: 'userId',
        as: 'user'
    })

Course schema:

const CourseSchema = new Schema({
  title: String,
  userId: {
    type: mongoose.Types.ObjectId,
    ref: 'User'
  },
}, { strict: false });

Expected output:

{
    "course": [
        {
            "_id": "64b000c085e9b1aec5975249",
            "title": "test",
            "userId": "64affc47d81a4bfba92b5540",
            "user": [], // data here should be present but empty why?
        }
    ]
}

How do I get user data in the above output?

Works using populate:

const course = await mongoose.model('Course')
            .find({ _id: new mongoose.Types.ObjectId(id) })
            .populate('userId').exec();

But how to achieve this using aggregation pipeline?

答案1

得分: 1

集合名称是 users,而不是 User。请查看选项:collection

> 默认情况下,Mongoose通过将模型名称传递给 utils.toCollectionName 方法来生成集合名称。该方法会将名称变为复数形式。

让我们看看 utils.toCollectionName 方法:

exports.toCollectionName = function(name, pluralize) {
  if (name === 'system.profile') {
    return name;
  }
  if (name === 'system.indexes') {
    return name;
  }
  if (typeof pluralize === 'function') {
    return pluralize(name);
  }
  return name;
};

function pluralize(str) {
  let found;
  str = str.toLowerCase();
  if (!~uncountables.indexOf(str)) {
    found = rules.filter(function(rule) {
      return str.match(rule[0]);
    });
    if (found[0]) {
      return str.replace(found[0][0], found[0][1]);
    }
  }
  return str;
}

请查看完整源代码

正如您所看到的,模型名称 User 将被转换为集合名称 users。因此,在 lookup() 方法中应该是 from: "users" 而不是 from: "User"

localFieldforeignField 是错误的。localField 是本地集合中的字段,即 courcesforeignField 是外部集合中的字段,即 users

  • from: <外部集合>
  • localField: <本地集合文档中的字段>,
  • foreignField: <外部集合文档中的字段>

一个有效的示例:

import mongoose from 'mongoose';
import util from 'util';
import { config } from '../../config';

mongoose.set('debug', true);
console.log(mongoose.version);

const UserSchema = new mongoose.Schema({ name: String });
const User = mongoose.model('User', UserSchema);

const CourseSchema = new mongoose.Schema(
	{
		title: String,
		userId: {
			type: mongoose.Types.ObjectId,
			ref: 'User',
		},
	},
	{ strict: false },
);
const Course = mongoose.model('Course', CourseSchema);

(async function main() {
	try {
		await mongoose.connect(config.MONGODB_URI);
		// seed
		const [u1] = await User.create([{ name: 'user-a' }]);
		const [c] = await Course.create([{ title: 'course a', userId: u1 }]);

		const r = await Course.aggregate()
			.match({ _id: c?._id })
			.lookup({
				from: 'users',
				localField: 'userId',
				foreignField: '_id',
				as: 'user',
			});

		console.log(util.inspect(r, false, null));
	} catch (error) {
		console.error(error);
	} finally {
		await mongoose.connection.close();
	}
})();

日志:

7.3.2
Mongoose: users.insertOne({ name: 'user-a', _id: ObjectId("64b0e2768c75418f819995af"), __v: 0 }, {})
Mongoose: courses.insertOne({ title: 'course a', userId: ObjectId("64b0e2768c75418f819995af"), _id: ObjectId("64b0e2778c75418f819995b1"), __v: 0}, {})
Mongoose: courses.aggregate([ { '$match': { _id: new ObjectId("64b0e2778c75418f819995b1") } }, { '$lookup': { from: 'users', localField: 'userId', foreignField: '_id', as: 'user' } }], {})
[
  {
    _id: new ObjectId("64b0e2778c75418f819995b1"),
    title: 'course a',
    userId: new ObjectId("64b0e2768c75418f819995af"),
    __v: 0,
    user: [
      {
        _id: new ObjectId("64b0e2768c75418f819995af"),
        name: 'user-a',
        __v: 0
      }
    ]
  }
]
英文:

The collection name is users, not User. See option: collection

> Mongoose by default produces a collection name by passing the model name to the utils.toCollectionName method. This method pluralizes the name.

Let's see the utils.toCollectionName method:

exports.toCollectionName = function(name, pluralize) {
  if (name === &#39;system.profile&#39;) {
    return name;
  }
  if (name === &#39;system.indexes&#39;) {
    return name;
  }
  if (typeof pluralize === &#39;function&#39;) {
    return pluralize(name);
  }
  return name;
};

function pluralize(str) {
  let found;
  str = str.toLowerCase();
  if (!~uncountables.indexOf(str)) {
    found = rules.filter(function(rule) {
      return str.match(rule[0]);
    });
    if (found[0]) {
      return str.replace(found[0][0], found[0][1]);
    }
  }
  return str;
}

See full source code

As you can see, the model name &#39;User&#39; will be converted to the collection name &quot;users&quot;. So it should be from: &quot;users&quot;, not from: &quot;User&quot; in lookup() method.

The localField and foreignField are wrong. The localField is the field in the local collection which is cources. The foreignField is the field in the foreign collection which is users.

  • from: &lt;foreign collection&gt;
  • localField: &lt;field from local collection&#39;s documents&gt;,
  • foreignField: &lt;field from foreign collection&#39;s documents&gt;

A working example:

import mongoose from &#39;mongoose&#39;;
import util from &#39;util&#39;;
import { config } from &#39;../../config&#39;;

mongoose.set(&#39;debug&#39;, true);
console.log(mongoose.version);

const UserSchema = new mongoose.Schema({ name: String });
const User = mongoose.model(&#39;User&#39;, UserSchema);

const CourseSchema = new mongoose.Schema(
	{
		title: String,
		userId: {
			type: mongoose.Types.ObjectId,
			ref: &#39;User&#39;,
		},
	},
	{ strict: false },
);
const Course = mongoose.model(&#39;Course&#39;, CourseSchema);

(async function main() {
	try {
		await mongoose.connect(config.MONGODB_URI);
		// seed
		const [u1] = await User.create([{ name: &#39;user-a&#39; }]);
		const [c] = await Course.create([{ title: &#39;course a&#39;, userId: u1 }]);

		const r = await Course.aggregate()
			.match({ _id: c?._id })
			.lookup({
				from: &#39;users&#39;,
				localField: &#39;userId&#39;,
				foreignField: &#39;_id&#39;,
				as: &#39;user&#39;,
			});

		console.log(util.inspect(r, false, null));
	} catch (error) {
		console.error(error);
	} finally {
		await mongoose.connection.close();
	}
})();

Logs:

7.3.2
Mongoose: users.insertOne({ name: &#39;user-a&#39;, _id: ObjectId(&quot;64b0e2768c75418f819995af&quot;), __v: 0 }, {})
Mongoose: courses.insertOne({ title: &#39;course a&#39;, userId: ObjectId(&quot;64b0e2768c75418f819995af&quot;), _id: ObjectId(&quot;64b0e2778c75418f819995b1&quot;), __v: 0}, {})
Mongoose: courses.aggregate([ { &#39;$match&#39;: { _id: new ObjectId(&quot;64b0e2778c75418f819995b1&quot;) } }, { &#39;$lookup&#39;: { from: &#39;users&#39;, localField: &#39;userId&#39;, foreignField: &#39;_id&#39;, as: &#39;user&#39; } }], {})
[
  {
    _id: new ObjectId(&quot;64b0e2778c75418f819995b1&quot;),
    title: &#39;course a&#39;,
    userId: new ObjectId(&quot;64b0e2768c75418f819995af&quot;),
    __v: 0,
    user: [
      {
        _id: new ObjectId(&quot;64b0e2768c75418f819995af&quot;),
        name: &#39;user-a&#39;,
        __v: 0
      }
    ]
  }
]

答案2

得分: 0

如果在Course集合中,您的userId是objectId而不是普通字符串,您可以按照以下查询进行聚合:

db.Course.aggregate([{$match:{_id:ObjectId("yourId")}}
,{$lookup:{
from: 'User',
localField: 'userId',
foreignField: "_id",
as: 'users'
}}])

如果您的userId是普通字符串,您需要使用$addFields将您的字符串id转换为objectId。

db.Course.aggregate([{$match:{_id:ObjectId("yourId")}}
,{$addFields:{'userId':{"$toObjectId":"$userId"}}}
,{$lookup:{
from: 'User',
localField: 'userId',
foreignField: "_id",
as: 'users'
}}])
英文:

If your userId in Course collection is objectId instead of plain String
you can aggregate result by following query,

db.Course.aggregate([{$match:{_id:ObjectId(&quot;yourId&quot;)}}
,{$lookup:{
from: &#39;User&#39;,
localField: &#39;userId&#39;,
foreignField: &quot;_id&quot;,
as: &#39;users&#39;
}}])

if you have userId as plain String you need to use $addFields to convert your string id to ObjectId.

db.Course.aggregate([{$match:{_id:ObjectId(&quot;yourId&quot;)}}
,{$addFields:{&#39;userId&#39;:{&quot;$toObjectId&quot;:&quot;$userId&quot;}}}
,{$lookup:{
from: &#39;User&#39;,
localField: &#39;userId&#39;,
foreignField: &quot;_id&quot;,
as: &#39;users&#39;
}}])

huangapple
  • 本文由 发表于 2023年7月14日 05:05:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/76683232.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定