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

Sequelize关联查询中动态Schema无法传递至关联表的解决方案咨询

问题:Sequelize动态Schema在关联查询中仅作用于主表,关联表未生效

使用TypeScript结合Sequelize编写复杂查询,定义了Scope和动态Schema,但关联查询(includes)时,仅主表应用了指定Schema,关联的Profile、Permission等表都没带上Schema前缀。

模型定义

@Scopes(() => ({
  ['simpleScope']: simpleScope,
  ['completeScope']: completeScope,
  pagination: paginationModelScope,
}))
@Table({
  createdAt: 'created_at',
  updatedAt: 'updated_at',
  deletedAt: 'deleted_at',
  paranoid: true,
  tableName: 'user',
})
export class User extends Model {
  @PrimaryKey
  @AutoIncrement
  @Column
  id: number;

  // [...] 其他字段

  @ForeignKey(() => Profile)
  @Column({ allowNull: false, type: DataType.INTEGER })
  profile_id: number;

  @BelongsTo(() => Profile)
  declare readonly profile: Profile;
}

@Scopes(() => ({
  ['simpleScope']: simpleScope,
  ['completeScope']: completeScope,
  pagination: paginationModelScope,
}))
@Table({
  createdAt: 'created_at',
  updatedAt: 'updated_at',
  deletedAt: 'deleted_at',
  paranoid: true,
  tableName: 'profile',
})
export class Profile extends Model {
  @PrimaryKey
  @AutoIncrement
  @Column(DataType.BIGINT)
  id: number;

  // [...] 其他字段

  @BelongsToMany(() => Permission, () => ProfilePermission)
  permission?: Permission[];
}

@Scopes(() => ({
  simpleScope: simpleScope,
  pagination: paginationModelScope,
}))
@Table({
  createdAt: 'created_at',
  updatedAt: 'updated_at',
  deletedAt: 'deleted_at',
  paranoid: true,
  tableName: 'permission',
})
export class Permission extends Model {
  @PrimaryKey
  @AutoIncrement
  @Column(DataType.BIGINT)
  id: number;

  // [...] 其他字段

  @ApiProperty({ type: () => Profile, nullable: true, isArray: true })
  @BelongsToMany(() => Profile, () => ProfilePermission)
  profile?: Profile[];
}

Scope定义

import { IncludeOptions } from 'sequelize';

export function completeScope(): IncludeOptions {
  return {
    attributes: {
      exclude: ['created_at', 'updated_at', 'deleted_at', 'senha'],
      include: [],
    },
    include: [
      {
        association: 'profile',
        required: true,
        paranoid: true,
        attributes: {
          exclude: ['created_at', 'updated_at', 'deleted_at'],
          include: [],
        },
        include: [
          {
            association: 'permission',
            required: true,
            paranoid: false,
            attributes: {
              exclude: ['created_at', 'updated_at', 'deleted_at'],
              include: [],
            },
          },
        ],
      },
    ],
  };
}

查询代码

public static async findOne<Response = any>(model, id, scope: any = 'completeScope', schema: string): Promise<Response> {
  const response = await model.schema(schema).scope([scope]).findOne({ where: { id } });

  if (response === null) {
    throw new NotFoundException('Not found!');
  }

  return response;
}

使用示例

async findOne(id, scope, schema) {
  return BasicCrud.findOne(this.UserModel, id, scope, schema);
}

生成的SQL

Executed (default): SELECT "User"."id", "User"."email", "User"."profile_id", "profile"."id" AS "profile.id", "profile"."name" AS "profile.name", "profile->permission"."id" AS "profile.permission.id", "profile->permission"."name" AS "profile.permission.name", "profile->permission->ProfilePermission"."profile_id" AS "profile.permission.ProfilePermission.profile_id", "profile->permission->ProfilePermission"."permission_id" AS "profile.permission.ProfilePermission.permission_id",
 "profile->permission->ProfilePermission"."created_at" AS "profile.permission.ProfilePermission.created_at", "profile->permission->ProfilePermission"."updated_at" AS "profile.permission.ProfilePermission.updated_at" FROM "SCHEMA"."User" AS "User" INNER JOIN "profile" AS "profile" ON "User"."profile_id" = "profile"."id" AND ("profile"."deleted_at" IS NULL) INNER JOIN ( "profile_permission" AS "profile->permission->ProfilePermission" INNER JOIN "permission" AS "profile->permission" ON "profile->permission"."id" = "profile->permission->ProfilePermission"."permission_id") ON "profile"."id" = "profile->permission->ProfilePermission"."profile_id" WHERE ("User"."deleted_at" IS NULL AND "User"."id" = 1);

可以看到,只有主表User带有指定的SCHEMA前缀,关联的profile、permission等表都没有应用该Schema。


解决方案

方法一:修改Scope函数,接收Schema参数并指定关联模型的Schema版本

将Scope函数改为可传入schema参数,在include配置中显式指定关联模型为对应Schema的实例:

import { IncludeOptions } from 'sequelize';
import { Profile, Permission } from './models'; // 导入关联模型

export function completeScope(schema: string): IncludeOptions {
  return {
    attributes: {
      exclude: ['created_at', 'updated_at', 'deleted_at', 'senha'],
      include: [],
    },
    include: [
      {
        association: 'profile',
        model: Profile.schema(schema), // 指定带Schema的Profile模型
        required: true,
        paranoid: true,
        attributes: {
          exclude: ['created_at', 'updated_at', 'deleted_at'],
          include: [],
        },
        include: [
          {
            association: 'permission',
            model: Permission.schema(schema), // 指定带Schema的Permission模型
            required: true,
            paranoid: false,
            attributes: {
              exclude: ['created_at', 'updated_at', 'deleted_at'],
              include: [],
            },
          },
        ],
      },
    ],
  };
}

然后修改查询函数,调用Scope时传入schema:

public static async findOne<Response = any>(model, id, scope: any = 'completeScope', schema: string): Promise<Response> {
  // 根据scope名称调用对应函数并传入schema
  const scopeOptions = typeof scope === 'string' 
    ? { [scope]: () => completeScope(schema) } 
    : scope;
  
  const response = await model.schema(schema).scope(scopeOptions).findOne({ where: { id } });

  if (response === null) {
    throw new NotFoundException('Not found!');
  }

  return response;
}

方法二:在查询时动态修改Scope的include配置

如果不想修改原Scope函数,可以在查询前动态遍历Scope的include选项,为每个关联模型添加Schema:

import { Includeable } from 'sequelize';

// 递归函数:为所有关联模型添加Schema
function applySchemaToIncludes(includes: Includeable[], schema: string) {
  if (!Array.isArray(includes)) includes = [includes];
  
  includes.forEach(include => {
    if ('model' in include && include.model) {
      include.model = include.model.schema(schema);
    }
    if (include.include) {
      applySchemaToIncludes(include.include, schema);
    }
  });
}

public static async findOne<Response = any>(model, id, scopeName: string = 'completeScope', schema: string): Promise<Response> {
  // 获取模型的Scope配置
  const scopeConfig = model.options.scopes[scopeName];
  // 克隆Scope配置避免修改原定义
  const scopeOptions = JSON.parse(JSON.stringify(scopeConfig));
  
  // 为所有关联模型应用Schema
  if (scopeOptions.include) {
    applySchemaToIncludes(scopeOptions.include, schema);
  }

  const response = await model.schema(schema).scope({ [scopeName]: scopeOptions }).findOne({ where: { id } });

  if (response === null) {
    throw new NotFoundException('Not found!');
  }

  return response;
}

这两种方法都能让关联表在查询时带上指定的Schema前缀,解决主表与关联表Schema不一致的问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 19:32:03