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

Mongoose调用save报错:IProtein类型无save属性的正确解决方法

解决Mongoose中Property 'save' does not exist on type 'IProtein'错误(无需继承mongoose.Document)

我在使用Mongoose的save()方法时遇到了类型错误:Property 'save' does not exist on type 'IProtein'.ts(2339)。我知道可以让接口继承mongoose.Document来解决,但官方不推荐这种方式,请问正确的解决方法是什么?

相关代码

模型定义:

import * as mongoose from "mongoose";

interface IProtein  {
    seller: mongoose.Schema.Types.ObjectId;
    name: string;
    description: string;
    price: Number;
    flavour: string;
}

const proteinSchema = new mongoose.Schema<IProtein>({
    seller: {
        type: mongoose.Schema.Types.ObjectId,
        required: true,
        ref: 'User'
    },
    name: {
        type: String,
        required: true,
        unique: true
    },
    description: {
        type: String,
        required: true
    },
    price: {
        type: Number,
        required: true
    },
    flavour: {
        type: String,
        required: true
    }
})

const Protein: mongoose.Model<IProtein> = mongoose.model('Protein', proteinSchema);
export { Protein, IProtein };

更新函数:

export const updateProtein = asyncHandler(async (req: any, res: any) => {
    const currentName: string = req.params.name
    const { name, description, price, flavour } : 
        { name: string | null, description: string | null, price: number | null, flavour: string | null } = req.body

    if (!name && !description && !price && !flavour) {
        return res.status(400).json({ message: 'No field got changed' })
    }

    const protein: Protein.IProtein | null = await Protein.Protein.findOne({ currentName }).lean().exec()

    if (!protein) {
        return res.status(400).json({ message: 'Protein not found' })
    }

    const duplicate: Protein.IProtein[] | null = await Protein.Protein.find({ currentName }).lean().exec()

    if (duplicate.length > 1) {
        return res.status(409).json({ message: 'Duplicate protein title' })
    }

    if (protein)
    {
        if (name)
            protein.name = name
        if (description)
            protein.description = description
        if (price)
            protein.price = price
        if (flavour)
            protein.flavour = flavour

        const updatedProtein = await protein.save() //This is the problem

        res.json(`'${updatedProtein.name}' updated`)
    }
})

问题根源

  1. .lean()方法的影响:你调用了.lean()查询数据,该方法返回的是普通JavaScript对象,并非Mongoose的Document实例,自然没有save()方法。
  2. 类型定义缺失关联:你的IProtein接口仅定义了数据结构,未关联Mongoose Document的方法类型,导致TypeScript无法识别save()。

正确解决方法

方案1:移除.lean()并使用HydratedDocument关联类型

Mongoose 6+推荐用HydratedDocument来关联数据接口和Document实例类型,无需继承mongoose.Document:

1.1 修改模型定义

import * as mongoose from "mongoose";

interface IProtein {
    seller: mongoose.Schema.Types.ObjectId;
    name: string;
    description: string;
    price: number; // 注意改用小写number(原始类型),避免大写Number包装对象
    flavour: string;
}

// 定义带Mongoose方法的文档实例类型
type ProteinDocument = mongoose.HydratedDocument<IProtein>;

const proteinSchema = new mongoose.Schema<IProtein>({
    seller: {
        type: mongoose.Schema.Types.ObjectId,
        required: true,
        ref: 'User'
    },
    name: {
        type: String,
        required: true,
        unique: true
    },
    description: {
        type: String,
        required: true
    },
    price: {
        type: Number,
        required: true
    },
    flavour: {
        type: String,
        required: true
    }
})

const Protein = mongoose.model<IProtein>('Protein', proteinSchema);
export { Protein, IProtein, ProteinDocument };

1.2 修改更新函数

import { Protein, ProteinDocument } from './path-to-your-model';

export const updateProtein = asyncHandler(async (req: any, res: any) => {
    const currentName: string = req.params.name
    const { name, description, price, flavour } = req.body as { 
        name?: string, 
        description?: string, 
        price?: number, 
        flavour?: string 
    };

    if (!name && !description && !price && !flavour) {
        return res.status(400).json({ message: '没有修改任何字段' })
    }

    // 移除.lean(),使用ProteinDocument类型
    const protein: ProteinDocument | null = await Protein.findOne({ name: currentName }).exec();

    if (!protein) {
        return res.status(400).json({ message: '未找到该产品' })
    }

    // 检查新名称是否重复(修复原代码逻辑错误)
    if (name) {
        const duplicate = await Protein.findOne({ name }).lean().exec();
        if (duplicate && duplicate._id.toString() !== protein._id.toString()) {
            return res.status(409).json({ message: '产品名称已存在' })
        }
    }

    // 更新字段
    if (name) protein.name = name;
    if (description) protein.description = description;
    if (price) protein.price = price;
    if (flavour) protein.flavour = flavour;

    const updatedProtein = await protein.save();
    res.json(`'${updatedProtein.name}' 已更新`)
})

方案2:使用findOneAndUpdate替代save(更高效)

如果无需修改文档实例再保存,直接用findOneAndUpdate可以跳过Document实例化步骤,更高效:

export const updateProtein = asyncHandler(async (req: any, res: any) => {
    const currentName: string = req.params.name
    const updates = {} as Partial<IProtein>;
    const { name, description, price, flavour } = req.body;

    // 构建更新对象
    if (name) updates.name = name;
    if (description) updates.description = description;
    if (price) updates.price = price;
    if (flavour) updates.flavour = flavour;

    if (Object.keys(updates).length === 0) {
        return res.status(400).json({ message: '没有修改任何字段' })
    }

    // 检查名称重复
    if (name) {
        const duplicate = await Protein.findOne({ name }).lean().exec();
        if (duplicate) {
            return res.status(409).json({ message: '产品名称已存在' })
        }
    }

    // 直接更新并返回新文档
    const updatedProtein = await Protein.findOneAndUpdate(
        { name: currentName },
        updates,
        { new: true, runValidators: true } // new返回更新后的数据,runValidators启用字段验证
    ).exec();

    if (!updatedProtein) {
        return res.status(400).json({ message: '未找到该产品' })
    }

    res.json(`'${updatedProtein.name}' 已更新`)
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 16:46:03