我正在将我的应用程序从express.js移到Nest.js,并且我找不到一种在另一种引用猫鼬模式的方法,而没有使用通过mongoose.Schema({... }。
让我们使用文档中的示例,以便我可以阐明我的问题:
@Schema()
export class Cat extends Document {
@Prop()
name: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
现在,我想要的是这样的东西:
@Schema()
export class Owner extends Document {
@Prop({type: [Cat], required: true})
cats: Cat[];
}
export const OwnerSchema = SchemaFactory.createForClass(Owner);
当我以这种方式定义架构时,会出现以下错误:无效的架构配置:Cat
无效
在数组cats
那么,使用这种更加面向对象的方法定义架构,在另一个架构中引用一个架构的正确方法是什么?
答案 0 :(得分:29)
我研究了源代码,并了解了如何通过SchemaFactory.createForClass
方法转换Schema类。
@Schema()
export class Cat extends Document {
@Prop()
name: string;
}
export const catSchema = SchemaFactory.createForClass(Cat);
基本上,当您执行SchemaFactory.createForClass(Cat)
Nest会将类语法转换为Mongoose模式语法,因此最后,转换结果将如下所示:
const schema = new mongoose.Schema({
name: { type: String } // Notice that `String` is now uppercase.
});
看看这个文件: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()
装饰器在后台执行的操作。
当您这样做时:
@Prop()
name: string;
Prop
函数将被调用,在这种情况下将不带参数。
const type = Reflect.getMetadata(TYPE_METADATA_KEY, target, propertyKey);
使用Reflect
API,我们可以获得执行name: string
时使用的数据类型。 type
变量的值现在设置为String
。请注意,它不是string
,Reflect
API始终会返回数据类型的构造函数版本,因此:
number
将序列化为Number
string
将被序列化为String
boolean
将序列化为Boolean
TypeMetadataStorage.addPropertyMetadata
然后将下面的对象存储到存储中。
{
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);
}
}
因此基本上,该对象将存储在properties
中的TypeMetadataStorageHost
变量中。
TypeMetadataStorageHost
是一个单例,将存储很多这些对象。
要了解SchemaFactory.createForClass(Cat)
如何产生猫鼬模式,请查看以下内容: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
类。
您可以在此处看到方法定义: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
时存储的对象:
[
{
target: User,
propertyKey: ‘name’,
options: { type: String }
}
]
forEach
将产生:
{
name: { type: String }
}
最后,它将用作mongoose.Schema
构造函数mongoose/schema.factory.ts at master · nestjs/mongoose · GitHub的参数:
return new mongoose.Schema(
schemaDefinition,
schemaMetadata && schemaMetadata.options,
);
您应该把Prop()
作为参数是什么?
还记得Nest何时forEach
生成猫鼬模式吗?
schemaMetadata.properties?.forEach((item) => {
const options = this.inspectTypeDefinition(item.options as any);
schemaDefinition = {
[item.propertyKey]: options as any,
…schemaDefinition,
};
});
要获取options
,它使用inspectTypeDefinition
方法。您可以看到以下定义:
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 }
,则将返回直接用作猫鼬选项。因此,要将引用从Cat
添加到Owner
,可以执行以下操作:
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
添加引用,我们可以这样做:
@Prop([{ type: MongooseSchema.Types.ObjectId, ref: Cat.name }])
要在评论部分回答有关以下问题:
如果您正确阅读了答案,那么您应该具有足够的知识来做到这一点。但是,如果您不这样做,这就是TLDR的答案。
请注意,我强烈建议您在进入此处之前先阅读整个答案。
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);
答案 1 :(得分:0)
import { Prop, raw, Schema, SchemaFactory } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';
import { Education } from '../../education/schemas';
import { RECORD_STATUS } from '../../common/common.constants';
import { Employment } from '../../employment/schemas';
import {
JOB_SEARCH_STATUS,
LANGUAGE_PROFICIENCY
} from '../user-profile.constants';
const externalLinks = {
linkedInUrl: { type: String },
githubUrl: { type: String },
twitterUrl: { type: String },
blogUrl: { type: String },
websiteUrl: { type: String },
stackoverflowUrl: { type: String }
};
const address = {
line1: { type: String, required: true },
line2: { type: String },
zipCode: { type: String },
cityId: { type: Number },
countryId: { type: Number }
};
const language = {
name: { type: String, require: true },
code: { type: String, required: true },
proficiency: { type: String, required: true, enum: LANGUAGE_PROFICIENCY }
};
const options = {
timestamps: true,
};
export type UserProfileDocument = UserProfile & mongoose.Document;
@Schema(options)
export class UserProfile {
_id: string;
@Prop()
firstName: string;
@Prop()
lastName: string;
@Prop()
headline: string;
@Prop({
unique: true,
trim: true,
lowercase: true
})
email: string;
@Prop()
phoneNumber: string
@Prop(raw({
jobSearchStatus: { type: String, enum: JOB_SEARCH_STATUS, required: true }
}))
jobPreferences: Record<string, any>;
@Prop(raw(externalLinks))
externalLinks: Record<string, any>;
@Prop([String])
skills: string[];
@Prop(raw({ type: address, required: false }))
address: Record<string, any>;
@Prop()
birthDate: Date;
@Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Employment' }] })
employments: Employment[];
@Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Education' }] })
educations: Education[];
@Prop(raw([language]))
languages: Record<string, any>[];
@Prop()
timeZone: string;
@Prop()
createdAt: Date;
@Prop()
updatedAt: Date;
@Prop({
enum: RECORD_STATUS,
required: true,
default: RECORD_STATUS.Active
})
recordStatus: string;
}
export const UserProfileSchema = SchemaFactory.createForClass(UserProfile);