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

使用TypeScript/JS客户端更新EdgeDB对象:关联与嵌套处理问题

EdgeDB 候选人教育详情增删改及嵌套对象处理方案

一、整合教育详情的删除操作

当前代码仅实现了教育详情的插入与更新,未处理候选人不再关联的教育记录。根据业务场景,可选择以下两种方案:

方案1:仅解除候选人与过时教育记录的关联(保留Education数据)

如果Education是可被多个候选人共享的资源,直接更新候选人的education_details关联列表即可——EdgeDB在设置multi关联为新列表时,会自动移除原有列表中不存在的关联:

// 原有教育详情upsert逻辑不变
let educationDetails = await e.params({ items: e.json }, (params) => {
  return e.for(e.json_array_unpack(params.items), (item) => {
    return e.insert(e.Education, {
      institution_name: e.cast(e.str, item.institution_name),
      course_name: e.cast(e.str, item.course_name),
      pass_percentage: e.cast(e.str, item.pass_percentage),
      course_start_month: e.cast(e.str, item.course_start_month),
      course_end_month: e.cast(e.str, item.course_end_month),
      course_start_year: e.cast(e.str, item.course_start_year),
      course_end_year: e.cast(e.str, item.course_end_year)
    }).unlessConflict(education => ({
      on: [education.institution_name, education.course_name],
      else: e.update(e.Education, () => ({
        set: {
          course_start_month: e.cast(e.str, item.course_start_month),
          course_end_month: e.cast(e.str, item.course_end_month),
          course_start_year: e.cast(e.str, item.course_start_year),
          course_end_year: e.cast(e.str, item.course_end_year),
          pass_percentage: e.cast(e.str, item.pass_percentage)
        }
      }))
    }));
  });
}).run(client, { items: education_details });

// 更新候选人时直接设置新关联列表,旧关联自动移除
await e.update(e.Candidate, (candidate) => ({
  filter: e.op(candidate.id, "=", e.uuid(id)),
  set: {
    first_name,
    last_name,
    dob: e.str(dob),
    primary_contact_number,
    alternate_contact_number,
    hobbies,
    profile_summary,
    education_details: e.select(educationDetails),
  }
})).run(client);

方案2:删除候选人不再关联的专属Education记录

如果Education是该候选人专属(不会被其他候选人引用),需先清理过时的教育记录:

// 1. 提取传入教育详情的唯一标识(课程名+机构名)
const incomingEducationKeys = education_details.map(item => 
  `${item.course_name}|${item.institution_name}`
);

// 2. 删除当前候选人关联的、不在传入列表的Education记录
await e.delete(e.Education, (edu) => ({
  filter: e.op(
    e.op(edu.course_name ++ "|" ++ edu.institution_name, "not in", incomingEducationKeys),
    "and",
    e.exists(e.Candidate, (c) => ({
      filter: e.op(c.id, "=", e.uuid(id)),
      predicate: e.op(c.education_details, "@=", edu)
    }))
  )
})).run(client);

// 3. 执行原有教育详情upsert和候选人更新逻辑(同方案1)
let educationDetails = await e.params({ items: e.json }, (params) => {
  return e.for(e.json_array_unpack(params.items), (item) => {
    return e.insert(e.Education, {
      institution_name: e.cast(e.str, item.institution_name),
      course_name: e.cast(e.str, item.course_name),
      pass_percentage: e.cast(e.str, item.pass_percentage),
      course_start_month: e.cast(e.str, item.course_start_month),
      course_end_month: e.cast(e.str, item.course_end_month),
      course_start_year: e.cast(e.str, item.course_start_year),
      course_end_year: e.cast(e.str, item.course_end_year)
    }).unlessConflict(education => ({
      on: [education.institution_name, education.course_name],
      else: e.update(e.Education, () => ({
        set: {
          course_start_month: e.cast(e.str, item.course_start_month),
          course_end_month: e.cast(e.str, item.course_end_month),
          course_start_year: e.cast(e.str, item.course_start_year),
          course_end_year: e.cast(e.str, item.course_end_year),
          pass_percentage: e.cast(e.str, item.pass_percentage)
        }
      }))
    }));
  });
}).run(client, { items: education_details });

await e.update(e.Candidate, (candidate) => ({
  filter: e.op(candidate.id, "=", e.uuid(id)),
  set: {
    first_name,
    last_name,
    dob: e.str(dob),
    primary_contact_number,
    alternate_contact_number,
    hobbies,
    profile_summary,
    education_details: e.select(educationDetails),
  }
})).run(client);

二、优雅处理嵌套Projects类型的更新

首先完善Schema,定义Project类型并与Education建立关联:

type Project {
  required project_name: str;
  required project_description: str;
  required education: Education;
  # 确保同一教育记录下的项目名称唯一
  constraint exclusive on ((.project_name, .education));
}

type Education {
  required course_name: str;
  required institution_name: str;
  required course_start_month: str;
  required course_start_year: str;
  required course_end_month: str;
  required course_end_year: str;
  pass_percentage: str;
  constraint exclusive on ((.course_name, .institution_name));
  multi projects: Project;
}

在处理Education的upsert时,嵌套实现Projects的增删改:

let educationDetails = await e.params({ items: e.json }, (params) => {
  return e.for(e.json_array_unpack(params.items), (item) => {
    return e.insert(e.Education, {
      institution_name: e.cast(e.str, item.institution_name),
      course_name: e.cast(e.str, item.course_name),
      pass_percentage: e.cast(e.str, item.pass_percentage),
      course_start_month: e.cast(e.str, item.course_start_month),
      course_end_month: e.cast(e.str, item.course_end_month),
      course_start_year: e.cast(e.str, item.course_start_year),
      course_end_year: e.cast(e.str, item.course_end_year),
      // 嵌套处理Projects的新增/更新
      projects: e.for(e.json_array_unpack(item.projects), (proj) => {
        return e.insert(e.Project, {
          project_name: e.cast(e.str, proj.project_name),
          project_description: e.cast(e.str, proj.project_description),
          education: e.select(e.Education, (edu) => ({
            filter: e.op(
              edu.course_name, "=", e.cast(e.str, item.course_name),
              "and",
              edu.institution_name, "=", e.cast(e.str, item.institution_name)
            )
          }))
        }).unlessConflict(proj => ({
          on: [proj.project_name, proj.education],
          else: e.update(e.Project, () => ({
            set: { project_description: e.cast(e.str, proj.project_description) }
          }))
        }));
      })
    }).unlessConflict(education => ({
      on: [education.institution_name, education.course_name],
      else: e.update(e.Education, () => ({
        set: {
          course_start_month: e.cast(e.str, item.course_start_month),
          course_end_month: e.cast(e.str, item.course_end_month),
          course_start_year: e.cast(e.str, item.course_start_year),
          course_end_year: e.cast(e.str, item.course_end_year),
          pass_percentage: e.cast(e.str, item.pass_percentage),
          // 更新Education时同步清理过时项目并upsert新项目
          projects: (() => {
            // 删除当前教育记录中不在传入列表的项目
            e.delete(e.Project, (p) => ({
              filter: e.op(
                p.project_name, "not in", e.json_array_unpack(item.projects).map(p => p.project_name),
                "and",
                p.education.id = education.id
              )
            }));
            // 新增/更新传入的项目
            return e.for(e.json_array_unpack(item.projects), (proj) => {
              return e.insert(e.Project, {
                project_name: e.cast(e.str, proj.project_name),
                project_description: e.cast(e.str, proj.project_description),
                education: education
              }).unlessConflict(proj => ({
                on: [proj.project_name, proj.education],
                else: e.update(e.Project, () => ({
                  set: { project_description: e.cast(e.str, proj.project_description) }
                }))
              }));
            });
          })()
        }
      }))
    }));
  });
}).run(client, { items: education_details });

// 候选人更新逻辑同前
await e.update(e.Candidate, (candidate) => ({
  filter: e.op(candidate.id, "=", e.uuid(id)),
  set: {
    first_name,
    last_name,
    dob: e.str(dob),
    primary_contact_number,
    alternate_contact_number,
    hobbies,
    profile_summary,
    education_details: e.select(educationDetails),
  }
})).run(client);

三、存JSON vs 关联类型的对比

将教育详情存为JSON是一种简化方案,但不推荐,核心原因:

  • 类型不安全:无法保证JSON内字段的类型、必填性,易出现数据不一致
  • 查询能力弱:无法针对教育详情或项目字段做复杂筛选、聚合
  • 缺乏约束:无法设置唯一性、关联关系等规则,难以维护数据完整性
  • 维护成本高:嵌套JSON的更新逻辑繁琐,无法利用EdgeDB的ORM特性

使用关联类型(Education、Project)可充分发挥EdgeDB的优势:类型安全、强约束、灵活查询、原生嵌套操作支持,是更简洁、可维护的方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 23:35:00