如何定义模型并使用 Mongoose 填充?

wmvff8tz  于 2023-01-21  发布在  Go
关注(0)|答案(5)|浏览(148)

背景

我在我的Node.JS应用程序中使用了mongoose和TypeScript,在从数据库获取数据时,我在很多地方使用了mongoose的populate
我所面临的问题是,我不知道如何输入模型,以便属性可以是ObjectId,也可以用来自另一个集合的数据填充。

我所尝试的

我尝试过在模型类型定义中使用联合类型,这看起来像是TypeScript提供的用于覆盖这些类型的东西:

interface User extends Document {
    _id: Types.ObjectId;
    name: string
}

interface Item extends Document {
    _id: Types.ObjectId;

    // Union typing here
    user: Types.ObjectId | User;
}

我的架构只将属性定义为带ref的ObjectId。

const ItemSchema = new Schema({
    user: { type: Schema.Types.ObjectId, ref: "User", index: true }
})
    • 示例**:

所以我可能会这样做:

ItemModel.findById(id).populate("user").then((item: Item) => {
    console.log(item.user.name);
})

这会产生编译错误:

[ts] Property 'name' does not exist on type 'User | ObjectId'.
     Property 'name' does not exist on type 'ObjectId'.

问题

如何才能使模型属性可以是TypeScript中的两种类型之一?

ztigrdn8

ztigrdn81#

您需要使用类型保护来将类型从Types.ObjectId | User缩小到User ...
如果你正在处理一个User类,你可以使用:

if (item.user instanceof User) {
    console.log(item.user.name);
} else {
    // Otherwise, it is a Types.ObjectId
}

如果你有一个与User匹配的结构,但它不是一个类的示例(例如,如果User是一个接口),你需要一个自定义类型保护:

function isUser(obj: User | any) : obj is User {
    return (obj && obj.name && typeof obj.name === 'string');
}

您可以将其用于:

if (isUser(item.user)) {
    console.log(item.user.name);
} else {
    // Otherwise, it is a Types.ObjectId
}

如果不想为此检查结构,可以使用discriminated union

oiopk7p5

oiopk7p52#

填充用户时,将item.user强制转换为User

ItemModel.findById(id).populate("user").then((item: Item) => {
    console.log((<User>item.user).name);
})
v440hwme

v440hwme3#

您可以使用@types/mongoose库中的PopulatedDoc类型。请参见mongoose.doc

q9rjltbz

q9rjltbz4#

Mongoose的TypeScript绑定导出一个PopulatedDoc类型,帮助您在TypeScript定义中定义填充的文档:

import { Schema, model, Document, PopulatedDoc } from 'mongoose';

// `child` is either an ObjectId or a populated document
interface Parent {
  child?: PopulatedDoc<Child & Document>,
  name?: string
}
const ParentModel = model<Parent>('Parent', new Schema({
  child: { type: 'ObjectId', ref: 'Child' },
  name: String
}));

interface Child {
  name?: string;
}
const childSchema: Schema = new Schema({ name: String });
const ChildModel = model<Child>('Child', childSchema);

ParentModel.findOne({}).populate('child').orFail().then((doc: Parent) => {
  // Works
  doc.child.name.trim();
})

下面是PopulatedDoc类型的简化实现,它有两个通用参数:已填充的文档类型PopulatedType和未填充的类型RawId。RawId默认为ObjectId。

type PopulatedDoc<PopulatedType, RawId = Types.ObjectId> = PopulatedType | RawId;

作为开发人员,您有责任在填充和未填充的文档之间强制执行强类型。

ParentModel.findOne({}).populate('child').orFail().then((doc: Parent) => {
  // `doc` doesn't have type information that `child` is populated
  useChildDoc(doc.child);
});

// You can use a function signature to make type checking more strict.
function useChildDoc(child: Child): void {
  console.log(child.name.trim());
}

这是从文档中提取的。您可以检查它here

cig3rfwq

cig3rfwq5#

抱歉迟到了,这里是要使用 typescript 填充的official way

ItemModel.findById(id).populate<{user: 
{ _id: ObjectId, name: string; email: string} 
}>("user").then((item: Item) => {
    console.log(item.user.name);
})

interface User { user: {_id: ObjectId, name: string; email: string} }

ItemModel.findById(id).populate<User>("user").then((item: Item) => {
    console.log(item.user.name);
})

相关问题