【问题标题】:NestJS - How to create nested schema with decoratorsNestJS - 如何使用装饰器创建嵌套模式
【发布时间】:2020-10-26 22:58:37
【问题描述】:

假设我想用 mongoose 构建以下架构:

const userSchema = new Schema({
  name: {
    firstName: String,
    lastName: String
  }
})

如何使用 NestJS 装饰器 (@Schema() & @Prop())?

我尝试了这个方法,但没有运气:

@Schema()
class Name {
  @Prop()
  firstName: string;

  @Prop()
  lastName: string;
}

@Schema()
class User extends Document {
  @Prop({ type: Name })
  name: Name;
}

我也不想使用raw() 方法。

【问题讨论】:

    标签: mongoose schema nestjs mongoose-schema


    【解决方案1】:

    我还没有发现 NestJS 的这一部分足够灵活。对我来说,一个可行的解决方案(经过测试)如下:

    @Schema({_id: false}) // _id:false is optional
    class Name {
      @Prop() // any options will be evaluated
      firstName: string; // data type will be checked
    
      @Prop()
      lastName: string;
    }
    
    @Schema()
    class User {
      @Prop({type: Name}) // {type: Name} can be omitted
      name: Name;
    }
    

    以这种方式定义架构将使所有内容(类装饰器、传递的选项、数据类型验证、NestJS 功能等)都按预期工作。唯一的“问题”是将为每个 @Schema 创建 _id 属性,您可能不希望这样,就像您的情况一样。您可以通过将{_id: false} 作为选项对象添加到@Schema() 来避免这种情况。请记住,不会阻止任何进一步的嵌套模式创建 _id 属性,例如

    这个:

    @Schema() // will create _id filed
    class Father {
      age: number;
      name: string;
    }
    
    @Schema({_id: false}) // won't create _id field
    class Parents {
      @Prop()
      father: Father;
    
      @Prop()
      mother: string;
    }
    
    @Schema()
    class Person {
      @Prop()
      parents: Parents;
    }
    

    会产生这个:

    {
      _id: ObjectId('someIdThatMongoGenerated'),
      parents: {
        father: {
          _id: ObjectId('someIdThatMongoGenerated'),
          age: 40,
          name: Jon Doe
        },
        mother: Jane Doe
      }
    }
    

    另一种解决方法是使用本机猫鼬在 NestJS 中创建架构,如下所示:

    const UserSchema = new mongoose.Schema({
      name: {
        firstName: {
          type: String, // note uppercase
          required: true // optional
        },
        lastName: {
          type: String,
          required: true
        }
      }
    });
    

    【讨论】:

      【解决方案2】:

      这是带有装饰器的嵌套模式的完整示例,full example on github

      user.entity.ts

      import { ObjectType, Field, Int, ID } from '@nestjs/graphql';
      import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
      import { School } from 'src/school/entities/school.entity';
      import * as mongoose from 'mongoose';
      export type UserDocument = User & Document;
      
      @ObjectType()
      @Schema()
      export class User {
        @Field((type) => ID,{ nullable: true })
        _id: mongoose.Types.ObjectId;
        @Field({ nullable: true })
        @Prop()
        firstname: string;
        @Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'School' })
        @Field(()=>School,{ nullable: true })
      
        school: School;
      }
      export const UserSchema = SchemaFactory.createForClass(User);
      

      school.entity.ts

      import { ObjectType, Field, Int, ID } from '@nestjs/graphql';
      import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
      import { Document } from 'mongoose';
      import { User } from 'src/user/entities/user.entity';
      import * as mongoose from 'mongoose';
      
      export type SchoolDocument = School & Document;
      @ObjectType()
      @Schema()
      export class School {
        @Field(() => ID,{ nullable: true })
        _id: string;
        @Prop()
        @Field(() => String,{ nullable: true })
        name: string;
        @Field(()=>[User],{nullable:true})
        @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }] })
        users:User[];
      }
      export const SchoolSchema= SchemaFactory.createForClass(School);
      

      您应该在解析器中添加@ResolveField,这样您就不会得到子文档的空值。 school.resolver.ts

      import { Resolver, Query, Mutation, Args, Int, ResolveField, Parent } from '@nestjs/graphql';
      import { SchoolService } from './school.service';
      import { School } from './entities/school.entity';
      import { CreateSchoolInput } from './dto/create-school.input';
      import { UpdateSchoolInput } from './dto/update-school.input';
      import { UserService } from 'src/user/user.service';
      
      @Resolver(() => School)
      export class SchoolResolver {
        constructor(private readonly schoolService: SchoolService,
          private readonly userService: UserService) {}
      
      
      
        @ResolveField()
        async users(@Parent() parent:School){
          return this.userService.findBySchool(parent._id);
        }
      }
      

      user.resolver.ts

      import {
        Resolver,
        Query,
        Mutation,
        Args,
        Int,
        ResolveField,
        Parent,
      } from '@nestjs/graphql';
      import { UserService } from './user.service';
      import { User } from './entities/user.entity';
      import { CreateUserInput } from './dto/create-user.input';
      import { UpdateUserInput } from './dto/update-user.input';
      import { SchoolService } from 'src/school/school.service';
      import { School } from 'src/school/entities/school.entity';
      
      @Resolver(() => User)
      export class UserResolver {
        constructor(
          private readonly userService: UserService,
          private readonly schoolService: SchoolService,
        ) {}
      
      ResolveField(() => School)
        async school(@Parent() parent: User) {
          return await this.schoolService.findBySchool(parent.school._id);
        }
      }
      

      【讨论】:

        【解决方案3】:

        你也可以用这个。

        @Schema()
        class User extends Document {
          @Prop({ type:  { firstName: String, lastName: String })
          name: Name;
        }
        

        【讨论】:

          【解决方案4】:

          这是我的方法,效果很好,不涉及删除@schema()

          // Nested Schema
          @Schema()
          export class BodyApi extends Document {
            @Prop({ required: true })
            type: string;
          
            @Prop()
            content: string;
          }
          export const BodySchema = SchemaFactory.createForClass(BodyApi);
          
          // Parent Schema
          @Schema()
          export class ChaptersApi extends Document {
            // Array example
            @Prop({ type: [BodySchema], default: [] })
            body: BodyContentInterface[];
          
            // Single example
            @Prop({ type: BodySchema })
            body: BodyContentInterface;
          }
          export const ChaptersSchema = SchemaFactory.createForClass(ChaptersApi);
          

          当您在架构上设置该选项时,这会正确保存并显示时间戳

          【讨论】:

          • 嗯,看起来很有希望。很快就会尝试一下?
          • 它会创建 2 个单独的集合吗?我想要一个集合。
          • @AnkitTanna 不,只会创建您在模块中传递给 MongooseModule.forFeature 的架构。
          • BodyContentInterface 应该和 BodyApi 一样吗?它没有在任何地方定义
          • @AlexanderK1987,你不需要将它传递给 MongooseModule,我也犯了同样的错误,一旦我删除了它们,这些集合就再也没有回来了。我想你必须坚持在他们自己的模块中使用这些模型,这是一个很好的做法。
          【解决方案5】:

          所做的更改:

          1. 子文档类没有 @Schema 装饰器
          2. 子文档类需要从'mongoose'扩展Document

          user.schema.ts

          import { Document } from 'mongoose';
          
          @Schema()
          export class User extends Document {
            @Prop({ type: Name })
            name: Name;
          }
          
          export const UserSchema = SchemaFactory.createForClass(User);
          
          

          name.schema.ts

          import { Document } from 'mongoose';
          
          export class Name extends Document {
            @Prop({ default: " " })
            firstName: string;
          
            @Prop({ default: " " })
            lastName: string;
          }
          

          【讨论】:

          • 它是否适用于 unique: true 对于子文档数组中的属性?
          • 此解决方案将正确创建嵌套对象 (user.name.firstName),但类型 (:string) 验证将不起作用。您将被允许在 firstName 字段中写入数字或其他类型。这不是一个有效的解决方案。
          【解决方案6】:

          尝试从嵌套的“名称”中删除 @schema() 装饰器,仅将其保留在文档的根目录中。

          还记得在根级别扩展“mongoose.Document”。

          import { Prop, Schema, SchemaFactory, } from '@nestjs/mongoose';
          import { Document  } from 'mongoose';
              
          class Name {
            @Prop()
            firstName: string;
          
            @Prop()
            lastName: string;
          }
          
          @Schema()
          class User extends Document {
            @Prop({ type: Name })
            name: Name;
          }
          export const userSchema = SchemaFactory.createForClass(user);
          

          【讨论】:

          • 有什么问题?您是否收到任何错误消息?我刚刚在我的项目中做了这个结构,它工作正常
          • 我不知道,我没有收到任何错误,它只是不起作用。尝试在像“firstName”这样的嵌套属性上设置一个默认值,默认值不会设置,说明有问题。
          【解决方案7】:

          首先,您应该在这种情况下使用猫鼬模式。简单明了:

          export const UserSchema = new mongoose.Schema(
            {
              name: [UserNameSchema],
            },
            {
              timestamps: true,
            },
          );
          

          如果你不喜欢这种方法,你应该遵循官方文档:

          @Prop(raw({
            firstName: { type: String },
            lastName: { type: String }
          }))
          details: Record<string, any>;
          

          【讨论】:

          • 我想在前端和后端都使用我的打字稿模型,并将这个模型保存在一个共享文件夹中。用这种方法,我不能再这样做了!
          • 完全不应该。因为模式和模型不同。您还应该定义接口文件。返回数据应与接口兼容。然后将界面分享给前端。使用 OpenAPI 生成器。
          • 问题清楚地说明了使用 Nest 装饰器。
          猜你喜欢
          • 2018-07-27
          • 2016-06-11
          • 1970-01-01
          • 2021-04-13
          • 2013-05-07
          • 2020-06-20
          • 2021-10-25
          • 2022-01-15
          • 1970-01-01
          相关资源
          最近更新 更多