如何计算集合中的所有文档并在控制器中使用 cont,MongoDB 和 Express.js?
How do I count all the documents in a collection and use the cont in a controller, with MongoDB and Express.js?
我正在开发 blogging application (click the link to see the GitHub repo) with Express (version 4.17.1), EJS 和 MongoDB(版本 4.0.10)。
尝试对帖子进行分页,我在控制器中执行了以下操作:
exports.getPosts = (req, res, next) => {
const perPage = 5;
const currPage = req.query.page ? parseInt(req.query.page) : 1;
let postsCount = 0;
const posts = Post.find({}, (err, posts) => {
postsCount = posts.length;
let pageDecrement = currPage > 1 ? 1 : 0;
let pageIncrement = postsCount >= perPage ? 1 : 0;
if (err) {
console.log('Error: ', err);
} else {
res.render('default/index', {
moment: moment,
layout: 'default/layout',
website_name: 'MEAN Blog',
page_heading: 'XPress News',
page_subheading: 'A MEAN Stack Blogging Application',
currPage: currPage,
posts: posts,
pageDecrement: pageDecrement,
pageIncrement: pageIncrement
});
}
})
.sort({
created_at: -1
})
.populate('category')
.limit(perPage)
.skip((currPage - 1) * perPage);
};
并且在视图中:
<a class="btn btn-primary <%= pageDecrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage - pageDecrement %>">← Newer Posts</a>
和
<a class="btn btn-primary <%= pageIncrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage + pageIncrement %>">Older Posts →</a>
除非帖子数量等于 perPage x N
,否则效果很好,其中 N 是一个整数,在这种情况下,"Older Posts" 按钮被禁用的时间太迟了一页。
那是因为postsCount = posts.length
统计之后的帖子,它们受到.skip((currPage - 1) * perPage)
的限制。
所以我需要计算来自 model/collection 的帖子并将计数变量 引入 控制器。
我的模型:
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
short_description: {
type: String,
required: true
},
full_text: {
type: String,
required: true
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
post_image: {
type: String,
required: false
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
module.exports = mongoose.model('Post', postSchema);
如何计算帖子集合中的所有文档并在帖子控制器中使用该数字?
阅读$facet。
New in version 3.4.
Processes multiple aggregation pipelines within a single stage on the
same set of input documents. Each sub-pipeline has its own field in
the output document where its results are stored as an array of
documents.
示例: 参见 here
db.collection.aggregate([
{
$facet: {
"count": [
{ $match: {} },
{ $count: "totalCount" }
],
"data": [
{ $match: {} },
{ $sort: { _id: -1 } },
{ $skip: 1 },
{ $limit: 2 }
]
}
}
])
猫鼬版本:
Model.aggregate([
{
$facet: {
"count": [
{ $match: {} },
{ $count: "totalCount" }
],
"data": [
{ $match: {} },
{ $sort: { _id: -1 } },
{ $skip: 1 },
{ $limit: 2 }
]
}
}
]).
then(res => console.log(res)).
catch(error => console.error('error', error));
如果是猫鼬,你应该使用这个:
https://mongoosejs.com/docs/api.html#aggregate_Aggregate-facet
官方 Mongodb 文档:
https://docs.mongodb.com/manual/reference/operator/aggregation/facet
总体思路是执行聚合而不是多次调用(1 次用于获取所需信息 + 1 次用于获取文档总数)
您当然可以执行 2 个单独的调用,但这会影响您的性能(对于小数据量影响不大,但仍然...)
因此,您可以使用 .find() 获取所有需要的数据,然后像这样获取计数:
https://mongoosejs.com/docs/api.html#model_Model.count
PS。顺便说一句,使用 async/await 而不是回调以避免回调地狱
使用 mongodb 聚合框架可以更轻松地完成此操作。
我们使用 $facet 聚合来获取分页数据以及文档总数。
在聚合框架中我们使用$lookup instead of mongoose populate. $lookup returns an array, to get the first item in array we use $arrayElemAt operator inside $addFields。
下面是应用于您的应用的代码:
(这里不需要第一个 $match 聚合,但我放在里面以防你将来可能需要它)
exports.getPosts = async (req, res, next) => {
const perPage = 5;
const currPage = req.query.page ? parseInt(req.query.page) : 1;
const skip = (currPage - 1) * perPage;
try {
const result = await Post.aggregate([{
$match: {},
},
{
$sort: {
created_at: -1,
},
},
{
$lookup: {
from: "categories",
localField: "category",
foreignField: "_id",
as: "category",
},
},
{
$addFields: {
category: {
$arrayElemAt: ["$category", 0],
},
},
},
{
$facet: {
totalRecords: [{
$count: "total",
}, ],
data: [{
$skip: skip,
},
{
$limit: perPage,
},
],
},
},
]);
let postsCount = result[0].totalRecords[0].total;
const pageCount = Math.ceil(postsCount / perPage);
const pageDecrement = currPage > 1 ? 1 : 0;
const pageIncrement = currPage < pageCount ? 1 : 0;
const posts = result[0].data;
res.render("default/index", {
moment: moment,
layout: "default/layout",
website_name: "MEAN Blog",
page_heading: "XPress News",
page_subheading: "A MEAN Stack Blogging Application",
currPage,
posts,
pageDecrement,
pageIncrement,
});
} catch (err) {
console.log("Error: ", err);
res.status(500).send("something went wrong");
}
};
顺便说一下,在 post 架构中,对于您使用 default: Date.now()
的日期字段,这将导致日期值始终是相同的值,它应该是这种格式:default: Date.now
我正在开发 blogging application (click the link to see the GitHub repo) with Express (version 4.17.1), EJS 和 MongoDB(版本 4.0.10)。
尝试对帖子进行分页,我在控制器中执行了以下操作:
exports.getPosts = (req, res, next) => {
const perPage = 5;
const currPage = req.query.page ? parseInt(req.query.page) : 1;
let postsCount = 0;
const posts = Post.find({}, (err, posts) => {
postsCount = posts.length;
let pageDecrement = currPage > 1 ? 1 : 0;
let pageIncrement = postsCount >= perPage ? 1 : 0;
if (err) {
console.log('Error: ', err);
} else {
res.render('default/index', {
moment: moment,
layout: 'default/layout',
website_name: 'MEAN Blog',
page_heading: 'XPress News',
page_subheading: 'A MEAN Stack Blogging Application',
currPage: currPage,
posts: posts,
pageDecrement: pageDecrement,
pageIncrement: pageIncrement
});
}
})
.sort({
created_at: -1
})
.populate('category')
.limit(perPage)
.skip((currPage - 1) * perPage);
};
并且在视图中:
<a class="btn btn-primary <%= pageDecrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage - pageDecrement %>">← Newer Posts</a>
和
<a class="btn btn-primary <%= pageIncrement == 0 ? 'disabled' : '' %>" href="/?page=<%= currPage + pageIncrement %>">Older Posts →</a>
除非帖子数量等于 perPage x N
,否则效果很好,其中 N 是一个整数,在这种情况下,"Older Posts" 按钮被禁用的时间太迟了一页。
那是因为postsCount = posts.length
统计之后的帖子,它们受到.skip((currPage - 1) * perPage)
的限制。
所以我需要计算来自 model/collection 的帖子并将计数变量 引入 控制器。
我的模型:
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
short_description: {
type: String,
required: true
},
full_text: {
type: String,
required: true
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category'
},
post_image: {
type: String,
required: false
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
module.exports = mongoose.model('Post', postSchema);
如何计算帖子集合中的所有文档并在帖子控制器中使用该数字?
阅读$facet。
New in version 3.4.
Processes multiple aggregation pipelines within a single stage on the same set of input documents. Each sub-pipeline has its own field in the output document where its results are stored as an array of documents.
示例: 参见 here
db.collection.aggregate([
{
$facet: {
"count": [
{ $match: {} },
{ $count: "totalCount" }
],
"data": [
{ $match: {} },
{ $sort: { _id: -1 } },
{ $skip: 1 },
{ $limit: 2 }
]
}
}
])
猫鼬版本:
Model.aggregate([
{
$facet: {
"count": [
{ $match: {} },
{ $count: "totalCount" }
],
"data": [
{ $match: {} },
{ $sort: { _id: -1 } },
{ $skip: 1 },
{ $limit: 2 }
]
}
}
]).
then(res => console.log(res)).
catch(error => console.error('error', error));
如果是猫鼬,你应该使用这个:
https://mongoosejs.com/docs/api.html#aggregate_Aggregate-facet
官方 Mongodb 文档:
https://docs.mongodb.com/manual/reference/operator/aggregation/facet
总体思路是执行聚合而不是多次调用(1 次用于获取所需信息 + 1 次用于获取文档总数)
您当然可以执行 2 个单独的调用,但这会影响您的性能(对于小数据量影响不大,但仍然...) 因此,您可以使用 .find() 获取所有需要的数据,然后像这样获取计数: https://mongoosejs.com/docs/api.html#model_Model.count
PS。顺便说一句,使用 async/await 而不是回调以避免回调地狱
使用 mongodb 聚合框架可以更轻松地完成此操作。
我们使用 $facet 聚合来获取分页数据以及文档总数。
在聚合框架中我们使用$lookup instead of mongoose populate. $lookup returns an array, to get the first item in array we use $arrayElemAt operator inside $addFields。
下面是应用于您的应用的代码: (这里不需要第一个 $match 聚合,但我放在里面以防你将来可能需要它)
exports.getPosts = async (req, res, next) => {
const perPage = 5;
const currPage = req.query.page ? parseInt(req.query.page) : 1;
const skip = (currPage - 1) * perPage;
try {
const result = await Post.aggregate([{
$match: {},
},
{
$sort: {
created_at: -1,
},
},
{
$lookup: {
from: "categories",
localField: "category",
foreignField: "_id",
as: "category",
},
},
{
$addFields: {
category: {
$arrayElemAt: ["$category", 0],
},
},
},
{
$facet: {
totalRecords: [{
$count: "total",
}, ],
data: [{
$skip: skip,
},
{
$limit: perPage,
},
],
},
},
]);
let postsCount = result[0].totalRecords[0].total;
const pageCount = Math.ceil(postsCount / perPage);
const pageDecrement = currPage > 1 ? 1 : 0;
const pageIncrement = currPage < pageCount ? 1 : 0;
const posts = result[0].data;
res.render("default/index", {
moment: moment,
layout: "default/layout",
website_name: "MEAN Blog",
page_heading: "XPress News",
page_subheading: "A MEAN Stack Blogging Application",
currPage,
posts,
pageDecrement,
pageIncrement,
});
} catch (err) {
console.log("Error: ", err);
res.status(500).send("something went wrong");
}
};
顺便说一下,在 post 架构中,对于您使用 default: Date.now()
的日期字段,这将导致日期值始终是相同的值,它应该是这种格式:default: Date.now