NestJS - 如何使用装饰器创建嵌套模式

28

假设我想使用Mongoose构建以下模式:

const userSchema = new Schema({
  name: {
    firstName: String,
    lastName: String
  }
})
< p >如何使用NestJS装饰器(@Schema() & @Prop())?

我尝试了这种方法,但没有成功:

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

  @Prop()
  lastName: string;
}

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

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


为什么不使用 raw() - Leonardo Marzo
7个回答

46

这是我的方法,非常有效,不需要删除@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);

当您在架构选项中设置了该选项时,它可以正确保存并显示时间戳


3
它会创建两个单独的集合吗?我想要一个单一的集合。 - Ankit Tanna
3
不,仅会创建您在模块中传递给MongooseModule.forFeature的架构。 - Sinandro
13
BodyContentInterface是否应该与BodyApi相同?它在任何地方都没有定义。 - Clashsoft
如果我们没有将子文档模式传递到 MongooseModule 中,那么我们应该如何在相应的 service.ts 中使用和填充子文档? - AlexanderK1987
2
@AlexanderK1987,你不需要将它传递到MongooseModule中,我之前也犯了同样的错误。一旦我将它们移除,那些集合就再也没有出现过了。我想你只需要在各自的模块中使用这些模型,这是一个很好的实践。 - uhum
显示剩余4条评论

13

我发现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中使用本地的mongoose来创建模式,如下所示:
const UserSchema = new mongoose.Schema({
  name: {
    firstName: {
      type: String, // note uppercase
      required: true // optional
    },
    lastName: {
      type: String,
      required: true
    }
  }
});

这个解决方案对我非常奏效,包括像 defaultget/set 这样的属性参数。希望我早点知道这种方法。 - Yari

6

修改内容:

  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;
}

1
在子文档数组中,对于unique: true的属性会起作用吗? - Ankit Tanna
1
这个解决方案可以正确地创建一个嵌套对象(user.name.firstName),但类型 (:string) 验证将不起作用。你可以写入一个数字或另一种类型到firstName字段中。这不是一个可行的解决方案。 - Andi Aleksandrov
我正在遵循相同的方法,但我的默认值没有在嵌套对象上设置。 - Unknown User

2
您也可以使用这个。
    @Schema()
    class User extends Document {
      @Prop({ type:  { firstName: String, lastName: String })
      name: Name;
    }

2
尝试将嵌套的“Name”中的@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);

有什么问题吗?你收到任何错误消息了吗?我刚在我的项目中使用了那个结构,它可以正常工作。 - Francisco Cardoso
我不知道,我没有收到任何错误信息,它只是无法工作。尝试在嵌套属性(如“firstName”)上设置默认值,但默认值不会被设置,显示存在问题。 - Sinandro
https://discordapp.com/channels/520622812742811698/606125380817911828/738145362635915275 - Samith Bharadwaj

1

顶级文档

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
import { FanNotification } from './notification.schema';

export type FanDocument = HydratedDocument<Fan>;

@Schema()
export class Fan {

  @Prop({ type: FanNotification, default: () => ({}) })
  notifications: FanNotification;

}

export const FanSchema = SchemaFactory.createForClass(Fan);

我使用default: () => ({})来创建默认值。

嵌入式文档

import { Prop, Schema } from '@nestjs/mongoose';
import { AllMethod } from 'common/types';
import { Schema as MongooseSchema } from 'mongoose';

@Schema({ _id: false })
export class FanNotification {
  @Prop({
    type: MongooseSchema.Types.Mixed,
    default: { sms: true, email: true },
  })
  chat: AllMethod;

}

为了确保@Prop()被识别,并防止自动收集创建,我传递了{ _id: false }

当子文档路径上的任何内容发生更改时,您应该使用markModified()来标记子文档。 - Alex

-5
首先,您应该在此情况下使用mongoose schema。它非常明确和简单:
export const UserSchema = new mongoose.Schema(
  {
    name: [UserNameSchema],
  },
  {
    timestamps: true,
  },
);

如果您不喜欢这种方法,您应该遵循官方文档:
@Prop(raw({
  firstName: { type: String },
  lastName: { type: String }
}))
details: Record<string, any>;

我想在前端和后端都使用我的TypeScript模型,并将此模型保存在共享文件夹中。但是采用这种方法,我无法再这样做了! - Sinandro
3
完全不应该,因为模式和模型是不同的。您还应该定义接口文件。返回的数据应该与接口兼容。然后使用OpenAPI生成器将接口共享给前端。 - Vương Nguyễn
问题明确说明要使用 Nest 装饰器。 - Ankit Tanna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接