Nest.js 中的猫鼬子文档 [英] Mongoose Subdocuments in Nest.js
问题描述
我正在将我的应用程序从 express.js 转移到 Nest.js,我找不到一种方法可以在另一个中引用一个 mongoose Schema,而不使用使用 mongoose.Schema({...}).
I'm moving my app from express.js to Nest.js, and I can't find a way to reference one mongoose Schema in another, without using old way of declaring Schema with mongoose.Schema({...}).
让我们使用文档中的示例,以便我可以澄清我的问题:
Let's use example from docs, so I can clarify my problem:
@Schema()
export class Cat extends Document {
@Prop()
name: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
现在,我想要的是这样的:
Now, what I want is something like this:
@Schema()
export class Owner extends Document {
@Prop({type: [Cat], required: true})
cats: Cat[];
}
export const OwnerSchema = SchemaFactory.createForClass(Owner);
当我以这种方式定义架构时,我会收到一个错误,类似于:无效的架构配置:Cat
is not a valid在数组中输入 cats
When I define schemas this way I'd get an error, something like this: Invalid schema configuration: Cat
is not a valid
type within the array cats
那么,使用这种更面向对象的方法来定义架构,在另一个架构中引用一个架构的正确方法是什么?
So, what is the proper way for referencing one Schema inside another, using this more OO approach for defining Schemas?
推荐答案
我深入研究了源代码,了解了如何通过 SchemaFactory.createForClass
方法转换 Schema 类.
I dug into the source code and learned how Schema class is converted by the SchemaFactory.createForClass
method.
@Schema()
export class Cat extends Document {
@Prop()
name: string;
}
export const catSchema = SchemaFactory.createForClass(Cat);
基本上,当你执行 SchemaFactory.createForClass(Cat)
Nest 会将 class 语法转换成 Mongoose schema 语法,所以最终转换的结果是这样的:
Nest will convert the class syntax into the Mongoose schema syntax, so in the end, the result of the conversion would be like this:
const schema = new mongoose.Schema({
name: { type: String } // Notice that `String` is now uppercase.
});
2.转换是如何工作的?
看看这个文件:mongoose/prop.decorator.ts at master · nestjs/mongoose · GitHub
export function Prop(options?: PropOptions): PropertyDecorator {
return (target: object, propertyKey: string | symbol) => {
options = (options || {}) as mongoose.SchemaTypeOpts<unknown>;
const isRawDefinition = options[RAW_OBJECT_DEFINITION];
if (!options.type && !Array.isArray(options) && !isRawDefinition) {
const type = Reflect.getMetadata(TYPE_METADATA_KEY, target, propertyKey);
if (type === Array) {
options.type = [];
} else if (type && type !== Object) {
options.type = type;
}
}
TypeMetadataStorage.addPropertyMetadata({
target: target.constructor,
propertyKey: propertyKey as string,
options,
});
};
}
在这里您可以看到 Prop()
装饰器在幕后做了什么.当你这样做时:
Here you could see what the Prop()
decorator does behind the scene.
When you do:
@Prop()
name: string;
Prop
函数将被调用,在这种情况下没有参数.
Prop
function would be called, in this case with no arguments.
const type = Reflect.getMetadata(TYPE_METADATA_KEY, target, propertyKey);
使用Reflect
API,我们可以获取您在执行name: string
时使用的数据类型.type
变量的值现在设置为 String
.请注意,它不是 string
,Reflect
API 将始终返回数据类型的构造函数版本,因此:
Using the Reflect
API, we can get the data type that you use when you do name: string
. The value of type
variable is now set to String
. Notice that it’s not string
, the Reflect
API will always return the constructor version of the data type so:
number
将被序列化为Number
string
将被序列化为String
boolean
将被序列化为Boolean
- 等等
number
will be serialized asNumber
string
will be serialized asString
boolean
will be serialized asBoolean
- and so on
TypeMetadataStorage.addPropertyMetadata
然后将下面的对象存储到存储中.
TypeMetadataStorage.addPropertyMetadata
will then store the object below into the store.
{
target: User,
propertyKey: ‘name’,
options: { type: String }
}
我们来看看:mongoose/type-metadata.storage.ts at master · nestjs/mongoose · GitHub
export class TypeMetadataStorageHost {
private schemas = new Array<SchemaMetadata>();
private properties = new Array<PropertyMetadata>();
addPropertyMetadata(metadata: PropertyMetadata) {
this.properties.push(metadata);
}
}
所以基本上该对象将被存储到 TypeMetadataStorageHost
中的 properties
变量中.TypeMetadataStorageHost
是一个将存储大量这些对象的单例.
So basically that object will be stored into the properties
variable in TypeMetadataStorageHost
.
TypeMetadataStorageHost
is a singleton that will store a lot of these objects.
要了解 SchemaFactory.createForClass(Cat)
如何生成 Mongoose 模式,请查看以下内容:mongoose/schema.factory.ts at master · nestjs/mongoose · GitHub
To understand how the SchemaFactory.createForClass(Cat)
produce the Mongoose schema, take a look at this: mongoose/schema.factory.ts at master · nestjs/mongoose · GitHub
export class SchemaFactory {
static createForClass(target: Type<unknown>) {
const schemaDefinition = DefinitionsFactory.createForClass(target);
const schemaMetadata = TypeMetadataStorage.getSchemaMetadataByTarget(
target,
);
return new mongoose.Schema(
schemaDefinition,
schemaMetadata && schemaMetadata.options,
);
}
}
最重要的部分是:const schemaDefinition = DefinitionsFactory.createForClass(target);
.请注意,这里的目标是您的 Cat
类.
The most important part is:
const schemaDefinition = DefinitionsFactory.createForClass(target);
. Notice that the target here is your Cat
class.
你可以在这里看到方法定义:mongoose/在 master · nestjs/mongoose · GitHub 上的定义.factory.ts
You could see the method definition here: mongoose/definitions.factory.ts at master · nestjs/mongoose · GitHub
export class DefinitionsFactory {
static createForClass(target: Type<unknown>): mongoose.SchemaDefinition {
let schemaDefinition: mongoose.SchemaDefinition = {};
schemaMetadata.properties?.forEach((item) => {
const options = this.inspectTypeDefinition(item.options as any);
schemaDefinition = {
[item.propertyKey]: options as any,
…schemaDefinition,
};
});
return schemaDefinition;
}
schemaMetadata.properties
包含您在执行TypeMetadataStorage.addPropertyMetadata
时存储的对象:
schemaMetadata.properties
contains the object that you stored when you did TypeMetadataStorage.addPropertyMetadata
:
[
{
target: User,
propertyKey: ‘name’,
options: { type: String }
}
]
forEach
将产生:
{
name: { type: String }
}
最后,它将被用作 mongoose.Schema
构造函数的参数 mongoose/schema.factory.ts at master · nestjs/mongoose · GitHub:
In the end, it will be used as the argument to the mongoose.Schema
constructor mongoose/schema.factory.ts at master · nestjs/mongoose · GitHub:
return new mongoose.Schema(
schemaDefinition,
schemaMetadata && schemaMetadata.options,
);
4.所以来回答这个问题:
你应该把什么作为 Prop()
参数?
还记得 Nest 什么时候使用 forEach
来生成 Mongoose Schema 吗?
Remember when Nest does the forEach
to generate the Mongoose Schema?
schemaMetadata.properties?.forEach((item) => {
const options = this.inspectTypeDefinition(item.options as any);
schemaDefinition = {
[item.propertyKey]: options as any,
…schemaDefinition,
};
});
为了获得 options
,它使用 inspectTypeDefinition
方法.你可以看到下面的定义:
To get the options
it uses inspectTypeDefinition
method. You could see the definition below:
private static inspectTypeDefinition(options: mongoose.SchemaTypeOpts<unknown> | Function): PropOptions {
if (typeof options === 'function') {
if (this.isPrimitive(options)) {
return options;
} else if (this.isMongooseSchemaType(options)) {
return options;
}
return this.createForClass(options as Type<unknown>);
} else if (typeof options.type === 'function') {
options.type = this.inspectTypeDefinition(options.type);
return options;
} else if (Array.isArray(options)) {
return options.length > 0
? [this.inspectTypeDefinition(options[0])]
: options;
}
return options;
}
您可以得出以下结论:
- 如果
options
是function
,例如String
或SchemaType
,它将直接返回并使用作为猫鼬选项. - 如果
options
是一个Array
,它将返回该数组的第一个索引并将其包装在一个数组中. - 如果
options
不是Array
或function
,例如,如果它只是一个普通的object
如{ type: String, required: true }
,会直接返回,作为Mongoose的选项使用.
- If the
options
is afunction
such asString
or aSchemaType
it will be returned directly and used as the Mongoose options. - If the
options
is anArray
, it will return the first index of that array and wrap it in an array. - If the
options
is not anArray
orfunction
, for example, if it’s only a plainobject
such as{ type: String, required: true }
, it will be returned directly and used as the Mongoose options.
答案
所以要添加一个从 Cat
到 Owner
的引用,你可以这样做:
Answer
So to add a reference from Cat
to Owner
, you could do:
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Schema as MongooseSchema } from 'mongoose';
import { Owner } from './owner.schema.ts';
@Schema()
export class Cat extends Document {
@Prop()
name: string;
@Prop({ type: MongooseSchema.Types.ObjectId, ref: Owner.name })
owner: Owner;
}
export const catSchema = SchemaFactory.createForClass(Cat);
关于如何添加一个从Owner
到Cat
的引用,我们可以这样做:
As for how to add a reference from Owner
to Cat
, we could do:
@Prop([{ type: MongooseSchema.Types.ObjectId, ref: Cat.name }])
更新
在评论部分回答关于以下问题:
Update
To answer the question in the comment section about:
如果你正确阅读了答案,你应该有足够的知识来做到这一点.但如果你没有,这里是 TLDR 答案.
If you read the answer properly, you should have enough knowledge to do this. But if you didn't, here's the TLDR answer.
请注意,我强烈建议您在前往此处之前阅读整个答案.
Note that I strongly recommend you to read the entire answer before you go here.
image-variant.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
@Schema()
export class ImageVariant {
@Prop()
url: string;
@Prop()
width: number;
@Prop()
height: number;
@Prop()
size: number;
}
export const imageVariantSchema = SchemaFactory.createForClass(ImageVariant);
image.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
import { imageVariantSchema, ImageVariant } from './imagevariant.schema';
@Schema()
export class Image extends Document {
@Prop({ type: imageVariantSchema })
large: ImageVariant;
@Prop({ type: imageVariantSchema })
medium: ImageVariant;
@Prop({ type: imageVariantSchema })
small: ImageVariant;
}
export const imageSchema = SchemaFactory.createForClass(Image);
这篇关于Nest.js 中的猫鼬子文档的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!