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

TypeORM一对多/多对一关联更新时设为null或不生效问题

Stage截止日期调整时子Task移动异常问题

已查阅所有能找到的类似帖子,所有解决方案均无效,可能是操作有误,特此求助。

功能说明

项目中存在Stage(阶段)、Task(任务)和Checkpoint(检查点),三者均有独立截止日期。一个Stage包含多个Task和Checkpoint,Task有时会关联Checkpoint。

此前无法灵活修改Stage的截止日期:若要延后Stage截止日期,必须将所有子Task和Checkpoint一并后移,否则它们将不再属于该Stage。

实体关联代码

@Entity()
class Program {
  @PrimaryGeneratedColumn()
  id: string;

  @OneToMany(() => Stage, (stage) => stage.program)
  stages: Promise<Stage[]>;
}

@Entity()
class Stage {
  @PrimaryGeneratedColumn()
  id: string;

  @OneToMany(() => Task, (task) => task.stage)
  tasks: Promise<Task[]>;

  @OneToOne(() => Checkpoint, (checkpoint) => checkpoint.stage, { eager: true })
  @JoinColumn()
  finalCheckpoint: Checkpoint;

  @OneToMany(() => Checkpoint, (checkpoint) => checkpoint.stage)
  intermediateCheckpoints: Promise<Checkpoint[]>;
}

@Entity()
class Checkpoint {
  @PrimaryGeneratedColumn()
  id: string;

  @ManyToOne(() => Stage, (stage) => stage.intermediateCheckpoints, { eager: true })
  @JoinColumn()
  stage: Stage;
}

@Entity()
class Task {
  @PrimaryGeneratedColumn()
  id: string;

  @ManyToOne(() => Stage, (stage) => stage.tasks, { eager: true })
  @JoinColumn()
  stage: Stage;

  @OneToOne(() => Checkpoint, { nullable: true })
  @JoinColumn()
  checkpoint: Checkpoint;
}

目标

实现Stage截止日期可前后调整超出原边界,子实体(Task和Checkpoint)可自动移动到符合新截止日期的Stage,即更新stageId到对应Stage。

现有代码流程

首先调用updateStage函数,满足条件时调用moveStageChildrenWithoutLimits移动子实体:

export async function updateStage(
  transactionalEntityManager: EntityManager,
  programId: string,
  stageId: string,
  authInfo: AuthorizationInfoDto,
  req: StageUpdate,

) {
  const program = await retrieveProgram(transactionalEntityManager, programId);
  const stage = await retrieveStage(transactionalEntityManager, stageId, programId); // joins the tasks and checkpoints

  if (req.forceUpdateDeadline && req.deadline) {
    await moveStageChildrenWithoutLimits(
      transactionalEntityManager,
      program,
      stage,
      req.deadline,
    ); // move tasks and checkpoints to the appropriate stage
  }

    stage.deadline = req.deadline;

  const updatedStage = await transactionalEntityManager.save(stage);
  return await toStageDto(updatedStage, authInfo);
}

移动子实体的核心函数:

async function moveStageChildrenWithoutLimits(
  transactionalEntityManager: EntityManager,
  program: Program,
  stage: Stage,
  newDeadlineReq: Date,
) {
  const stages = await program.stages;
  const stageNewDeadline = new Date(newDeadlineReq).getTime();
  const stageOldDeadline = new Date(stage.deadline).getTime();
  const stageIndex = stages.findIndex((s) => s.id === stage.id);

  // 新截止日期早于原日期
  if (stageNewDeadline < stageOldDeadline) {
    const tasks = await stage.tasks;
    const tasksToMove = tasks.filter(
      (task) => task.deadline.getTime() > stageNewDeadline,
    );
    const tasksToUnlink = tasks.filter(
      (task) =>
        task.deadline.getTime() < stageNewDeadline && task.checkpointId,
    );

    const intermediateCheckpoints = await stage.intermediateCheckpoints;
    const intermediateCheckpointsToMove = intermediateCheckpoints.filter(
      (checkpoint) =>
        checkpoint.deadline.getTime() > stageNewDeadline &&
        stage.finalCheckpoint.id !== checkpoint.id,
    );
    const nextStage = stages[stageIndex + 1];

    // 将任务移动到下一个阶段
    const taskPromises = tasksToMove.map(async (task) => {
      task.stage = nextStage;
      return transactionalEntityManager.getRepository(Task).save(task);
    });
    await Promise.all(taskPromises);

    // 解除与下一阶段检查点关联的任务
    const tasksUnlinked = tasksToUnlink.map(async (task) => {
      const linkedCheckpointRelevance = task.checkpointId;
      const checkpointRelevanceDB = await transactionalEntityManager
        .getRepository(CheckpointRelevance)
        .findOne({
          where: { id: linkedCheckpointRelevance },
          relations: ['checkpoint'],
        });

      const checkpointDeadline = checkpointRelevanceDB.checkpoint.deadline.getTime();
      if (checkpointDeadline > stageNewDeadline) {
        task.checkpoint = null;
        transactionalEntityManager
          .getRepository(CheckpointRelevance)
          .softDelete({
            id: linkedCheckpointRelevance,
          });
        return transactionalEntityManager
          .getRepository(Task)
          .save(task);
      }
    });
    await Promise.all(tasksUnlinked);

    // 将中间检查点移动到下一阶段
    const checkpointPromises = intermediateCheckpointsToMove.map(
      async (checkpoint) => {
        checkpoint.stage = nextStage;
        await transactionalEntityManager
          .getRepository(Checkpoint)
          .save(checkpoint);
      },
    );
    await Promise.all(checkpointPromises);
  }

  // 新截止日期晚于原日期
  if (stageNewDeadline > stageOldDeadline) {
    const nextStage = stages[stageIndex + 1];
    const tasks = await nextStage.tasks;
    const tasksToMove = tasks.filter(
      (task) => task.deadline.getTime() < stageNewDeadline,
    );
    const intermediateCheckpoints = await nextStage.intermediateCheckpoints;
    const intermediateCheckpointsToMove = intermediateCheckpoints.filter(
      (checkpoint) =>
        checkpoint.deadline.getTime() < stageNewDeadline &&
        nextStage.finalCheckpoint.id !== checkpoint.id,
    );

    const tasksPromise = tasksToMove.map(async (task) => {
      // 问题点:此处设置后数据库stageId变为null,完全不生效
      task.stage = stage;
      const linkedCheckpointRelevance = task.checkpointId;

      if (linkedCheckpointRelevance) {
        const checkpointRelevanceDB = await transactionalEntityManager
          .getRepository(CheckpointRelevance)
          .findOne({
            where: { id: linkedCheckpointRelevance },
            relations: ['checkpoint'],
          });

        const checkpointDeadline = checkpointRelevanceDB.checkpoint.deadline.getTime();
        if (checkpointDeadline > stageNewDeadline) {
          task.checkpoint = null;
          transactionalEntityManager
            .getRepository(CheckpointRelevance)
            .softDelete({
              id: linkedCheckpointRelevance,
            });
        }
      }
      return transactionalEntityManager.getRepository(Task).save(task);
    });
    await Promise.all(tasksPromise);

    const checkpointsPromise = intermediateCheckpointsToMove.map(
      async (checkpoint) => {
        checkpoint.stage = stage;
        return transactionalEntityManager
          .getRepository(Checkpoint)
          .save(checkpoint);
      },
    );
    await Promise.all(checkpointsPromise);
  }
}

问题描述

当Stage新截止日期晚于原日期时,将下一个Stage的Task移动到当前Stage时,执行task.stage = stage后,数据库中的stageId被设置为null,完全不生效。

已尝试方案

  • 使用QueryBuilder更新无效
  • 使用raw sql的.query()方法更新无效,但直接在数据库执行相同查询有效
  • 尝试在Stage实体上添加新Task,同样无效

版本信息

  • typeorm v0.3.20(从v0.3.16升级而来)
  • postgres: v14.1 - alpine
  • node v18.16

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:54:54