名称: bknd-delete-entity 描述: 当从Bknd中移除实体时使用。覆盖安全删除实体、处理关系与依赖、数据备份、带–drop标志的同步工作流以及清理孤立数据。
删除实体
安全地从Bknd中移除实体(表),处理依赖关系并避免数据丢失。
先决条件
- 已存在具有实体的Bknd应用(参见
bknd-create-entity) - 对于代码模式:需访问
bknd.config.ts - 关键: 删除前备份数据库
警告:破坏性操作
删除实体:
- 永久移除表及其所有数据
- 移除所有涉及此实体的关系
- 可能破坏引用此实体的应用程序代码
- 无法撤销,除非数据库恢复
何时使用UI vs 代码模式
使用UI模式当
- 快速原型清理
- 开发/测试环境
- 探索存在的依赖关系
使用代码模式当
- 生产环境更改
- 需要版本控制
- 团队协作
- 可复现的部署
删除前检查清单
在删除实体之前,验证:
1. 检查关系
实体可能通过以下方式被其他实体引用:
- 外键(多对一)
- 联结表(多对多)
- 自引用
2. 检查数据
const api = app.getApi();
const count = await api.data.count("要删除的实体");
console.log(`要删除的记录数: ${count.data.count}`);
3. 检查代码引用
在代码库中搜索:
- 查询中的实体名称:
"实体名称" - 类型引用:
DB["实体名称"] - API调用:
api.data.*("实体名称")
4. 备份数据(如需)
// 删除前导出数据
const api = app.getApi();
const allRecords = await api.data.readMany("要删除的实体", {
limit: 100000,
});
// 保存到文件
import { writeFileSync } from "fs";
writeFileSync(
"备份-要删除的实体.json",
JSON.stringify(allRecords.data, null, 2)
);
代码方法
步骤1:识别依赖关系
检查模式中的关系:
// 查找涉及此实体的关系
const schema = em(
{
users: entity("users", { email: text().required() }),
posts: entity("posts", { title: text().required() }),
comments: entity("comments", { body: text() }),
},
({ relation }, { users, posts, comments }) => {
// posts 依赖于 users(外键)
relation(posts).manyToOne(users);
// comments 依赖于 posts(外键)
relation(comments).manyToOne(posts);
}
);
依赖顺序重要: 先删除子实体,后删除父实体。
步骤2:先移除关系
如果实体是关系的目标,更新模式以移除它们:
// 之前:posts 引用 users
const schema = em(
{
users: entity("users", { email: text().required() }),
posts: entity("posts", { title: text().required() }),
},
({ relation }, { users, posts }) => {
relation(posts).manyToOne(users);
}
);
// 之后:在删除 users 前移除关系
const schema = em({
users: entity("users", { email: text().required() }),
posts: entity("posts", { title: text().required() }),
});
步骤3:从模式中移除实体
简单地从您的bknd.config.ts中移除实体定义:
// 之前
const schema = em({
users: entity("users", { email: text().required() }),
posts: entity("posts", { title: text().required() }),
deprecated_entity: entity("deprecated_entity", { data: text() }),
});
// 之后 - 实体已移除
const schema = em({
users: entity("users", { email: text().required() }),
posts: entity("posts", { title: text().required() }),
});
步骤4:预览更改
# 查看将删除的内容(干运行)
npx bknd sync
输出显示:
要删除的表: deprecated_entity
受影响的列: (其他表上无)
步骤5:应用删除
# 使用 drop 标志应用(破坏性)
npx bknd sync --drop
或使用 force(启用所有破坏性操作):
npx bknd sync --force
步骤6:清理代码
移除所有引用:
- 删除类型定义
- 移除 API 调用
- 更新导入
UI方法
步骤1:打开管理面板
导航到http://localhost:1337(或您配置的URL)。
步骤2:转到数据部分
点击侧边栏中的Data。
步骤3:选择实体
点击您要删除的实体。
步骤4:检查依赖关系
查找:
- Relations 选项卡/部分显示连接的实体
- 关于依赖关系的警告消息
步骤5:导出数据(可选)
如果您需要数据:
- 转到实体的数据视图
- 导出或手动复制记录
- 外部保存备份
步骤6:删除实体
- 打开实体设置(齿轮图标或设置选项卡)
- 查找Delete Entity或Remove按钮
- 确认删除
- 实体和所有数据被移除
步骤7:同步数据库
删除后,确保数据库同步:
- 如果提示,点击Sync Database
- 或从CLI运行
npx bknd sync --drop
处理依赖关系
场景:实体具有子记录
问题: 删除users时,posts有users_id外键。
解决方案1:先删除子实体
// 1. 删除所有引用 users 的 posts
const api = app.getApi();
await api.data.deleteMany("posts", {});
// 2. 然后删除 users
//(通过模式移除 + 同步)
解决方案2:先移除关系
// 1. 从模式中移除关系
// 2. 同步以移除外键
// 3. 从模式中移除实体
// 4. 再次使用 --drop 同步
场景:实体是联结表目标
问题: tags在posts_tags联结表中使用。
解决方案:
// 1. 移除多对多关系
const schema = em(
{
posts: entity("posts", { title: text() }),
tags: entity("tags", { name: text() }),
}
// 移除:({ relation }, { posts, tags }) => { relation(posts).manyToMany(tags); }
);
// 2. 同步以删除联结表
// npx bknd sync --drop
// 3. 移除 tags 实体
const schema = em({
posts: entity("posts", { title: text() }),
});
// 4. 再次同步以删除 tags 表
// npx bknd sync --drop
场景:自引用实体
问题: categories引用自身(父/子)。
解决方案:
// 1. 移除自引用关系
const schema = em({
categories: entity("categories", { name: text() }),
// 移除自引用关系定义
});
// 2. 同步以移除外键
// npx bknd sync --drop
// 3. 移除实体
//(然后再次同步)
删除多个实体
顺序重要。按依赖顺序删除(先子后父):
// 依赖树:
// users <- posts <- comments
// <- likes
// 删除顺序:
// 1. comments(依赖于 posts)
// 2. likes(依赖于 posts)
// 3. posts(依赖于 users)
// 4. users(无依赖)
批量删除脚本
// scripts/cleanup-entities.ts
import { App } from "bknd";
async function cleanup() {
const app = new App({
connection: { url: process.env.DB_URL! },
});
await app.build();
const api = app.getApi();
// 按顺序删除
const entitiesToDelete = ["comments", "likes", "posts"];
for (const entity of entitiesToDelete) {
const count = await api.data.count(entity);
console.log(`从 ${entity} 删除 ${count.data.count} 条记录...`);
await api.data.deleteMany(entity, {});
console.log(`已删除所有来自 ${entity} 的记录`);
}
console.log("数据清理完成。现在从模式中移除并同步。");
}
cleanup().catch(console.error);
常见陷阱
外键约束错误
错误: Cannot drop table: foreign key constraint
原因: 另一个实体引用了此实体。
修复: 先移除关系,同步,然后移除实体。
联结表未删除
问题: 移除多对多关系后,联结表仍存在。
修复: 运行npx bknd sync --drop以包括破坏性操作。
实体仍显示在UI中
问题: 从代码中删除,但仍在管理面板中显示。
修复:
- 确保您运行了
npx bknd sync --drop - 重启Bknd服务器
- 清除浏览器缓存
应用程序在删除后崩溃
问题: 代码仍引用已删除的实体。
修复:
- 搜索代码库:
grep -r "实体名称" src/ - 移除所有API调用、类型、导入
- 修复TypeScript错误
意外删除错误实体
问题: 删除了生产数据。
修复:
- 如果有备份:从备份恢复
- 如果无备份:数据永久丢失
- 预防:删除前始终备份
验证
删除后
# 1. 检查模式导出(实体应不存在)
npx bknd schema --pretty | grep 实体名称
# 2. 验证同步状态
npx bknd sync
# 应显示无待处理更改
通过代码
const api = app.getApi();
// 对于已删除的实体,这应该失败/返回错误
try {
await api.data.readMany("已删除的实体", { limit: 1 });
console.log("错误:实体仍存在!");
} catch (e) {
console.log("确认:实体已成功删除");
}
通过REST API
# 应返回404或错误
curl http://localhost:1337/api/data/已删除的实体
该做与不该做
该做:
- 删除前备份数据
- 先检查依赖关系
- 先删除子实体,后删除父实体
- 使用
npx bknd sync预览后再用--drop - 删除后移除代码引用
- 生产前在开发环境中测试
不该做:
- 删除具有活动外键的实体
- 不预览更改就使用
--drop - 生产环境无备份删除
- 假设UI删除处理所有清理
- 忘记移除TypeScript类型/代码引用
相关技能
- bknd-create-entity - 创建新实体
- bknd-modify-schema - 修改现有模式
- bknd-define-relationship - 理解关系依赖
- bknd-crud-delete - 删除个别记录(非表)
- bknd-seed-data - 从备份恢复数据