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

无法为Sequelize查询结果对象动态添加属性问题排查

问题:Sequelize实例添加自定义字段后返回前端丢失

我尝试查询数据库获取用户的firstname和lastname,通过对象映射动态添加到返回前端的结果中,但最终返回值里没有这些字段。以下是相关代码、实际返回和预期返回:

代码实现

const getMyCrushRequest = async (req, res) => {
    // Check if user has sent any crush requests
    const findCrush = await CrushRequest.findAll({ 
        attributes: {exclude: ['requestFrom']}, 
        where: {requestFrom: req.user.id}
    });

    // If user has not sent any crush requests
    if(!findCrush || findCrush.length == 0){
        return res.status(StatusCodes.OK).send({
            success: false, 
            message: 'you have not sent any crush requests'
        });
    }

    
    // Map the crush requests to include the first and last name of the recipient
    const requests = await Promise.all(findCrush.map(async (crush) => {
        const nameQuery = await User.findOne({ 
            attributes: ["firstname", "lastname"], 
            where: {id: crush.requestTo}
        });

        // Add the user details to the crush request object
        crush.firstname = nameQuery.firstname;
        crush.lastname = nameQuery.lastname;

        return crush;
    }));
     
    // console.log (JSON.stringify(requests))
    // Return the mapped crush requests
    return res.status(StatusCodes.OK).json({
        success: true, 
        message: 'Crush request retrieved successfully', 
        requests: requests
    });
}

实际返回结果

{
    "success": true,
    "message": "Crush request retrieved successfully",
    "requests": [
        {
            "id": 2,
            "requestTo": "d8b60bcb-c1f4-41c9-8f0b-4f3baf734b06",
            "requestStatus": "pending",
            "createdAt": "2022-12-12T16:45:02.000Z",
            "updatedAt": "2022-12-12T16:45:02.000Z"
        },
        {
            "id": 3,
            "requestTo": "f6516bd8-2577-468c-9779-3f271b6a03db",
            "requestStatus": "pending",
            "createdAt": "2022-12-12T17:06:12.000Z",
            "updatedAt": "2022-12-12T17:06:12.000Z"
        }
    ]
}

预期返回结果

{
    "success": true,
    "message": "Crush request retrieved successfully",
    "requests": [
        {
            "id": 2,
            "requestTo": "d8b60bcb-c1f4-41c9-8f0b-4f3baf734b06",
            "firstname": "Somename", 
            "lastname": "someorthername",
            "requestStatus": "pending",
            "createdAt": "2022-12-12T16:45:02.000Z",
            "updatedAt": "2022-12-12T16:45:02.000Z"
        },
        {
            "id": 3,
            "requestTo": "f6516bd8-2577-468c-9779-3f271b6a03db", 
            "firstname": "Somename", 
            "lastname": "someorthername",
            "requestStatus": "pending",
            "createdAt": "2022-12-12T17:06:12.000Z",
            "updatedAt": "2022-12-12T17:06:12.000Z"
        }
    ]
}

问题原因

Sequelize查询返回的是模型实例对象,而非普通JavaScript对象。这类实例有自己的属性序列化规则,直接给实例添加的自定义属性(比如crush.firstname)不会被包含在默认的JSON输出里。

解决方案

方案1:转为普通对象后添加属性

在处理每个crush实例时,先将其转为普通JS对象,再添加自定义字段。可以用Sequelize实例的get({ plain: true })方法,或者ES6扩展运算符:

const requests = await Promise.all(findCrush.map(async (crush) => {
    const nameQuery = await User.findOne({ 
        attributes: ["firstname", "lastname"], 
        where: {id: crush.requestTo}
    });
    // 转为普通对象
    const crushObj = crush.get({ plain: true });
    // 添加属性
    crushObj.firstname = nameQuery.firstname;
    crushObj.lastname = nameQuery.lastname;
    return crushObj;
}));

方案2:使用关联查询(更高效,避免N+1查询)

上面的写法会触发N+1查询问题(先查N条CrushRequest,再循环查N次User表),性能较差。更优的方式是利用Sequelize的关联查询,一次SQL获取所有数据:

第一步:定义模型关联

在CrushRequest模型文件中添加关联:

CrushRequest.belongsTo(User, { 
  foreignKey: 'requestTo', 
  as: 'recipient' // 定义别名,方便后续查询
});

第二步:修改查询代码

直接在findAll时关联查询User表,指定需要的属性:

const getMyCrushRequest = async (req, res) => {
    // 一次查询获取所有数据,包括收件人姓名
    const findCrush = await CrushRequest.findAll({ 
        attributes: {exclude: ['requestFrom']}, 
        where: {requestFrom: req.user.id},
        include: [{
            model: User,
            as: 'recipient', // 和关联定义的别名一致
            attributes: ['firstname', 'lastname'] // 仅获取需要的字段
        }]
    });

    if(!findCrush || findCrush.length == 0){
        return res.status(StatusCodes.OK).send({
            success: false, 
            message: 'you have not sent any crush requests'
        });
    }

    // 调整数据结构,将关联字段合并到主对象中
    const requests = findCrush.map(crush => {
        const crushObj = crush.get({ plain: true });
        // 合并recipient的字段
        crushObj.firstname = crushObj.recipient.firstname;
        crushObj.lastname = crushObj.recipient.lastname;
        delete crushObj.recipient; // 删除多余的关联属性
        return crushObj;
    });

    return res.status(StatusCodes.OK).json({
        success: true, 
        message: 'Crush request retrieved successfully', 
        requests: requests
    });
}

这种方式既解决了字段丢失的问题,又优化了查询性能,避免了多次数据库请求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 19:10:27