【发布时间】:2019-07-29 18:13:48
【问题描述】:
我正在客户端使用 Angular 8 和服务器端使用 MongoDB 4 / Mongoose 5 的 NodeJS 12 构建应用程序。我有一个由Angular2 query builder 模块生成的查询。 Angular 查询构建器对象被发送到服务器。
我有一个服务器端控制器函数converts the Angular query object to MongoDB operations。这非常适合为顶级属性生成查询,例如RecordID 和RecordType。这也适用于构建嵌套和/或条件。
不过,我还需要支持查询子文档数组(示例架构中的“Items”数组)。
架构
这是我尝试查询的示例架构:
{
RecordID: 123,
RecordType: "Item",
Items: [
{
Title: "Example Title 1",
Description: "A description 1"
},
{
Title: "Example 2",
Description: "A description 2"
},
{
Title: "A title 3",
Description: "A description 3"
},
]
}
工作示例
仅限顶级属性
以下是查询生成器输出的示例,其中和/或仅针对顶级属性的条件:
{ "condition": "or", "rules": [ { "field": "RecordID", "operator": "=", "value": 1 }, { "condition": "and", "rules": [ { "field": "RecordType", "operator": "=", "value": "Item" } ] } ] }
这是仅在顶级属性上转换为 MongoDB 操作后的查询生成器输出:
{ '$expr': { '$or': [ { '$eq': [ '$RecordID', 1 ] }, { '$and': [ { '$eq': [ '$RecordType', 'Item' ] } ] } ] }}
将角度查询对象转换为 mongodb 运算符。
这是现有的查询转换函数
const conditions = { "and": "$and", "or": "$or" };
const operators = { "=": "$eq", "!=": "$ne", "<": "$lt", "<=": "$lte", ">": "$gt", ">=": "$gte" };
const mapRule = rule => ({
[operators[rule.operator]]: [ "$"+rule.field, rule.value ]
});
const mapRuleSet = ruleSet => {
return {
[conditions[ruleSet.condition]]: ruleSet.rules.map(
rule => rule.operator ? mapRule(rule) : mapRuleSet(rule)
)
}
};
let mongoDbQuery = { $expr: mapRuleSet(q) };
console.log(mongoDbQuery);
问题
该函数仅适用于顶级属性,例如 RecordID 和 RecordType,但我需要扩展它以支持子文档的 Items 数组。
显然,要查询嵌套子文档数组中的属性,必须使用$elemMatch 运算符,基于this related question。但是,就我而言,$expr 是构建嵌套和/或条件所必需的,因此我不能简单地切换到 $elemMatch。
问题
如何扩展查询转换功能,使其也支持 $elemMatch 来查询子文档数组?有没有办法让 $expr 工作?
UI 查询生成器
这里是 UI 查询构建器,其中包含嵌套的“Items”子文档数组。在此示例中,结果应匹配 RecordType 等于 "Item" AND Items.Title 等于 "Example Title 1" OR Items.Title contains "Example"。
这是 UI 查询生成器生成的输出。注意:field 和 operator 属性值是可配置的。
{"condition":"and","rules":[{"field":"RecordType","operator":"=","value":"Item"},{"condition":"or","rules":[{"field":"Items.Title","operator":"=","value":"Example Title 1"},{"field":"Items.Title","operator":"contains","value":"Example"}]}]}
更新:我可能已经找到了一种查询格式,它也适用于 $elemMatch 的嵌套和/或条件。我不得不删除 $expr 运算符,因为 $elemMatch 在表达式中不起作用。我的灵感来自answer to this similar question。
这是有效的查询。下一步是让我弄清楚如何调整查询生成器转换函数来创建查询。
{
"$and": [{
"RecordType": {
"$eq": "Item"
}
},
{
"$or": [{
"RecordID": {
"$eq": 1
}
},
{
"Items": {
"$elemMatch": {
"Title": { "$eq": "Example Title 1" }
}
}
}
]
}
]
}
【问题讨论】:
-
澄清一下:在 MongoDB 中,您有两种方法可以查询这样的数组:1) $elemMatch 可用于查找至少一个匹配所有条件的元素 2) 像“Items.Title”这样的点表示法将尝试找到任何
Example Title 1,所有其他条件将单独应用,因此不必应用于相同的数组元素。有什么方法可以确定您尝试应用哪种过滤方式? -
你能在
const conditions = { "and": "$and", "or": "$or" };中定义其他属性吗,比如添加elemMatch例如:conditions = { "and": "$and", "or": "$or", "elemMatch": " $elemMatch" };` -
@pengz 我明白你的意思。不幸的是,这里没有好的解决方案,因为
Item的两个条件都显示为单独的rules,因此以前的解决方案在这里可以按预期工作。请记住,您也可以在数据库中存储这样的文档:{ Items: { Title: "Example Title 1" } },因此库本身应该有一种方法来区分数组和嵌套文档。 -
@pengz 所以在以前的解决方案中,您可以忽略使用点符号指定路径的所有字段,例如
Items.Title,然后您需要遍历您的结构并将它们累积到单个$elemMatch -
只是为了澄清:为什么您的最终查询语法类似于
{ '$eq': [ '$RecordID', 1 ] }而不是{ 'RecordID': { '$eq': 1} }或{ 'RecordID': 1 }?