You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Typegoose父对象Ref返回null,子对象已关联父对象

问题描述

我尝试结合TypeGraphQL实现Typegoose文档中的虚拟填充示例,代码实现如下。在Apollo Studio中,先创建Person对象,再创建关联该Person的Kitten对象后,查询Kitten能看到正确的parent信息,但查询Person的pet字段始终为null。期望Person的pet字段能显示关联的Kitten,请问是代码存在错误,还是对Typegoose Ref的功能理解有误?


子对象Kitten类型定义

export enum Behavior {
  A = "good kitten",
  B = "very good kitten",
  C = "naughty kitten",
}

@ObjectType()
export class Kitten {
  @Field((type) => ID)
  readonly _id: string;

  @Field((type) => String)
  @prop({ required: false })
  parent: Ref<Person>;

  @Field((type) => String)
  @prop({ type: () => String, required: true })
  name: string;

  @Field((type) => String)
  @prop({
    type: () => String,
    required: true,
    enum: Behavior,
    default: Behavior.B,
  })
  behavior?: Behavior;
}

父对象Person类型定义

@ObjectType()
export class Person {
  @Field((type) => ID)
  readonly _id: string;

  @Field((type) => String)
  @prop({ type: () => String, required: true })
  name: string;

  @Field((type) => [Kitten], { nullable: true })
  @prop({
    ref: () => Kitten,
    localField: () => "_id",
    foreignField: () => "parent",
  })
  pet?: Ref<Kitten>[];
}

输入类型定义

@InputType()
export class NewKitten {
  @Field((type) => String)
  parent: Ref<Person>;

  @Field((type) => String)
  name: string;

  @Field((type) => String, { defaultValue: Behavior.B })
  behavior: Behavior;
}


@InputType()
export class NewPerson {
  @Field((type) => String)
  name: string;

  @Field((type) => [NewKitten], { nullable: true })
  pet?: Ref<NewKitten>[];
}

模型处理文件

import { Person } from "../types/person.type";
import { Kitten } from "../types/kitten.type";
import { getModelForClass } from "@typegoose/typegoose";

export const PersonModel = getModelForClass(Person);
export const KittenModel = getModelForClass(Kitten);

Resolver定义

@Resolver()
export class KittenResolver {
  @Query((returns) => [Kitten])
  async kittens(): Promise<Kitten[]> {
    return await KittenModel.find();
  }

  @Mutation((returns) => Kitten)
  async addKitten(@Arg("addKitten") addKitten: NewKitten): Promise<Kitten> {
    return await KittenModel.create(addKitten);
  }
}

@Resolver()
export class PersonResolver {
  @Query((returns) => [Person])
  async people(): Promise<Person[]> {
    return await PersonModel.find();
  }

  @Mutation((returns) => Person)
  async addPerson(@Arg("addPerson") addPerson: NewPerson): Promise<Person> {
    return await PersonModel.create(addPerson);
  }
}

package.json依赖

{
  "name": "ref-typegoose-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "ts-node-dev --respawn --transpile-only src/index.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@typegoose/typegoose": "^9.11.0",
    "apollo-server": "^3.10.0",
    "class-validator": "^0.13.2",
    "config": "^3.3.7",
    "cors": "^2.8.5",
    "graphql": "^15.3.0",
    "mongoose": "^6.5.2",
    "reflect-metadata": "^0.1.13",
    "type-graphql": "^1.1.1"
  },
  "devDependencies": {
    "@types/config": "^3.3.0",
    "ts-node-dev": "^2.0.0",
    "typescript": "^4.7.4"
  }
}

问题分析与解决方案

你的代码存在两处关键错误,导致虚拟填充未生效:

1. 虚拟填充的装饰器使用错误

Typegoose中实现虚拟填充需要使用@virtualPopulate()装饰器,而非普通的@prop()。你在Person类的pet字段上用了@prop()并传入ref/localField/foreignField,这是错误的用法——这些参数是@virtualPopulate()的配置项,不是@prop()的。

修改Person类的pet字段定义:

@ObjectType()
export class Person {
  // ... 其他字段

  @Field((type) => [Kitten], { nullable: true })
  @virtualPopulate({
    ref: () => Kitten,
    localField: "_id",
    foreignField: "parent",
  })
  pet?: Kitten[]; // 这里不需要Ref,因为虚拟填充返回的是实际文档
}

2. 查询时未触发虚拟填充

虚拟填充不会自动执行,必须在查询时调用.populate()方法指定要填充的虚拟字段。修改PersonResolver中的查询方法:

@Resolver()
export class PersonResolver {
  @Query((returns) => [Person])
  async people(): Promise<Person[]> {
    // 调用populate触发虚拟填充
    return await PersonModel.find().populate("pet");
  }

  // ... 其他方法
}

额外的输入类型问题

NewPerson中的pet字段定义不合理:因为创建Person时,Kitten还未被创建(或者不需要在这里关联),虚拟填充是基于Kitten中的parent字段反向关联的,所以NewPerson不需要pet字段,直接移除即可:

@InputType()
export class NewPerson {
  @Field((type) => String)
  name: string;
  // 移除pet字段
}

这样修改后,查询Person时就能正确返回关联的Kitten列表了。

内容的提问来源于stack exchange,提问作者M G

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 13:54:19