name: bknd-crud-update description: 当通过SDK或REST API更新Bknd实体中的现有记录时使用。涵盖updateOne、updateMany、更新关系($set、$add、$remove、$unset)、部分更新、条件更新、响应处理和常见模式。
CRUD 更新
使用SDK或REST API更新Bknd数据库中的现有记录。
前提条件
- Bknd项目正在运行(本地或部署)
- 实体存在且有记录可更新
- SDK配置好或API端点已知
- 记录ID或筛选条件已知
何时使用UI模式
- 快速一次性编辑
- 手动数据修正
- 开发期间的可视化验证
UI步骤: 管理面板 > 数据 > 选择实体 > 点击记录 > 编辑字段 > 保存
何时使用代码模式
- 应用逻辑中的用户编辑
- 表单提交
- 批量更新
- 自动化数据维护
代码方法
步骤1:设置SDK客户端
import { Api } from "bknd";
const api = new Api({
host: "http://localhost:7654",
});
// 如果需要认证:
api.updateToken("your-jwt-token");
步骤2:更新单个记录
使用 updateOne(entity, id, data):
const { ok, data, error } = await api.data.updateOne("posts", 1, {
title: "更新后的标题",
status: "published",
});
if (ok) {
console.log("更新帖子:", data.id);
} else {
console.error("失败:", error.message);
}
步骤3:处理响应
响应对象:
type UpdateResponse = {
ok: boolean; // 成功/失败
data?: { // 更新后的记录(如果ok)
id: number;
// ...所有字段的新值
};
error?: { // 错误信息(如果!ok)
message: string;
code: string;
};
};
步骤4:部分更新
只需提供更改的字段——其他字段保持不变:
// 仅更新标题,保留其他所有内容
await api.data.updateOne("posts", 1, {
title: "仅新标题",
});
// 更新多个字段
await api.data.updateOne("users", 5, {
name: "新名称",
bio: "更新后的简介",
updated_at: new Date().toISOString(),
});
步骤5:更新关系
更改链接记录(多对一)
// 将帖子作者更改为用户ID 2
await api.data.updateOne("posts", 1, {
author: { $set: 2 },
});
取消链接记录(设置为NULL)
// 移除作者链接
await api.data.updateOne("posts", 1, {
author: { $unset: true },
});
多对多:添加关系
// 向现有标签添加标签4和5
await api.data.updateOne("posts", 1, {
tags: { $add: [4, 5] },
});
多对多:移除关系
// 从帖子中移除标签2
await api.data.updateOne("posts", 1, {
tags: { $remove: [2] },
});
多对多:替换所有关系
// 用新集合替换所有标签
await api.data.updateOne("posts", 1, {
tags: { $set: [1, 3, 5] },
});
组合字段和关系更新
await api.data.updateOne("posts", 1, {
title: "更新后的帖子",
status: "published",
author: { $set: newAuthorId },
tags: { $add: [newTagId] },
});
步骤6:更新多个记录(批量)
使用 updateMany(entity, where, data):
// 归档所有草稿帖子
const { ok, data } = await api.data.updateMany(
"posts",
{ status: { $eq: "draft" } }, // where子句(必需)
{ status: "archived" }, // 更新值
);
// data包含受影响的记录
console.log("归档了", data.length, "个帖子");
重要: where子句是必需的,以防止意外更新所有记录。
// 按作者更新帖子
await api.data.updateMany(
"posts",
{ author_id: { $eq: userId } },
{ author_id: newUserId },
);
// 更新旧记录
await api.data.updateMany(
"sessions",
{ last_active: { $lt: "2024-01-01" } },
{ expired: true },
);
REST API方法
更新一个
curl -X PATCH http://localhost:7654/api/data/posts/1 \
-H "Content-Type: application/json" \
-d '{"title": "Updated Title", "status": "published"}'
带认证更新
curl -X PATCH http://localhost:7654/api/data/posts/1 \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{"title": "Protected Update"}'
更新关系
# 更改作者
curl -X PATCH http://localhost:7654/api/data/posts/1 \
-H "Content-Type: application/json" \
-d '{"author": {"$set": 2}}'
# 添加标签
curl -X PATCH http://localhost:7654/api/data/posts/1 \
-H "Content-Type: application/json" \
-d '{"tags": {"$add": [4, 5]}}'
更新多个
curl -X PATCH "http://localhost:7654/api/data/posts?where=%7B%22status%22%3A%22draft%22%7D" \
-H "Content-Type: application/json" \
-d '{"status": "archived"}'
React集成
编辑表单
import { useApp } from "bknd/react";
import { useState, useEffect } from "react";
function EditPostForm({ postId }: { postId: number }) {
const { api } = useApp();
const [title, setTitle] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 加载现有数据
useEffect(() => {
api.data.readOne("posts", postId).then(({ data }) => {
if (data) setTitle(data.title);
});
}, [postId]);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
const { ok, error: apiError } = await api.data.updateOne("posts", postId, {
title,
});
setLoading(false);
if (!ok) {
setError(apiError.message);
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="帖子标题"
/>
<button type="submit" disabled={loading}>
{loading ? "保存中..." : "保存"}
</button>
{error && <p className="error">{error}</p>}
</form>
);
}
使用SWR重新验证
import useSWR, { mutate } from "swr";
function useUpdatePost() {
const { api } = useApp();
async function updatePost(id: number, updates: object) {
const { ok, data, error } = await api.data.updateOne("posts", id, updates);
if (ok) {
// 重新验证帖子和列表
mutate(`posts/${id}`);
mutate("posts");
}
return { ok, data, error };
}
return { updatePost };
}
乐观更新
function useOptimisticUpdate() {
const { api } = useApp();
const [posts, setPosts] = useState<Post[]>([]);
async function updatePost(id: number, updates: Partial<Post>) {
// 乐观:立即更新
const originalPosts = [...posts];
setPosts((prev) =>
prev.map((p) => (p.id === id ? { ...p, ...updates } : p))
);
// 实际更新
const { ok } = await api.data.updateOne("posts", id, updates);
if (!ok) {
// 失败时回滚
setPosts(originalPosts);
}
return { ok };
}
return { posts, updatePost };
}
完整示例
import { Api } from "bknd";
const api = new Api({ host: "http://localhost:7654" });
// 认证
await api.auth.login({ email: "user@example.com", password: "password" });
// 简单字段更新
const { ok, data } = await api.data.updateOne("posts", 1, {
title: "Updated Title",
updated_at: new Date().toISOString(),
});
// 更新并更改关系
await api.data.updateOne("posts", 1, {
status: "published",
published_at: new Date().toISOString(),
category: { $set: 3 }, // 更改分类
tags: { $add: [7, 8] }, // 添加新标签
});
// 批量更新:将旧草稿标记为已归档
await api.data.updateMany(
"posts",
{
status: { $eq: "draft" },
created_at: { $lt: "2024-01-01" },
},
{ status: "archived" }
);
// 切换布尔字段
const post = await api.data.readOne("posts", 1);
if (post.ok) {
await api.data.updateOne("posts", 1, {
featured: !post.data.featured,
});
}
常见模式
Upsert(更新或插入)
async function upsert(
api: Api,
entity: string,
where: object,
data: object
) {
const { data: existing } = await api.data.readOneBy(entity, { where });
if (existing) {
return api.data.updateOne(entity, existing.id, data);
}
return api.data.createOne(entity, data);
}
// 使用
await upsert(
api,
"settings",
{ key: { $eq: "theme" } },
{ key: "theme", value: "dark" }
);
条件更新
async function updateIf(
api: Api,
entity: string,
id: number,
condition: (record: any) => boolean,
updates: object
) {
const { data: current } = await api.data.readOne(entity, id);
if (!current || !condition(current)) {
return { ok: false, error: { message: "条件未满足" } };
}
return api.data.updateOne(entity, id, updates);
}
// 仅当未发布时更新
await updateIf(
api,
"posts",
1,
(post) => post.status !== "published",
{ title: "New Title" }
);
递增/递减字段
async function increment(
api: Api,
entity: string,
id: number,
field: string,
amount: number = 1
) {
const { data: current } = await api.data.readOne(entity, id);
if (!current) return { ok: false };
return api.data.updateOne(entity, id, {
[field]: current[field] + amount,
});
}
// 递增浏览计数
await increment(api, "posts", 1, "view_count");
// 递减库存
await increment(api, "products", 5, "stock", -1);
软删除
async function softDelete(api: Api, entity: string, id: number) {
return api.data.updateOne(entity, id, {
deleted_at: new Date().toISOString(),
});
}
async function restore(api: Api, entity: string, id: number) {
return api.data.updateOne(entity, id, {
deleted_at: null,
});
}
批量更新并显示进度
async function batchUpdate(
api: Api,
entity: string,
updates: Array<{ id: number; data: object }>,
onProgress?: (done: number, total: number) => void
) {
const results = [];
for (let i = 0; i < updates.length; i++) {
const { id, data } = updates[i];
const result = await api.data.updateOne(entity, id, data);
results.push(result);
onProgress?.(i + 1, updates.length);
}
return results;
}
// 使用
await batchUpdate(
api,
"products",
[
{ id: 1, data: { price: 19.99 } },
{ id: 2, data: { price: 29.99 } },
{ id: 3, data: { price: 39.99 } },
],
(done, total) => console.log(`${done}/${total}`)
);
常见陷阱
记录未找到
问题: 更新返回无数据或错误。
修复: 首先验证记录是否存在:
const { data: existing } = await api.data.readOne("posts", id);
if (!existing) {
throw new Error("帖子未找到");
}
await api.data.updateOne("posts", id, updates);
无效关系ID
问题: FOREIGN KEY约束失败
修复: 验证相关记录是否存在:
// 错误 - 作者ID不存在
await api.data.updateOne("posts", 1, { author: { $set: 999 } });
// 正确 - 先验证
const { data: author } = await api.data.readOne("users", newAuthorId);
if (author) {
await api.data.updateOne("posts", 1, { author: { $set: newAuthorId } });
}
唯一约束冲突
问题: 更新为已存在值时出现UNIQUE约束失败。
修复: 更新前检查唯一性:
// 检查邮箱是否已被其他用户使用
const { data: existing } = await api.data.readOneBy("users", {
where: {
email: { $eq: newEmail },
id: { $ne: currentUserId }, // 排除当前用户
},
});
if (existing) {
throw new Error("邮箱已被使用");
}
await api.data.updateOne("users", currentUserId, { email: newEmail });
未检查响应
问题: 假设成功而未验证。
修复: 始终检查ok:
// 错误
const { data } = await api.data.updateOne("posts", 1, updates);
console.log(data.title); // data可能未定义!
// 正确
const { ok, data, error } = await api.data.updateOne("posts", 1, updates);
if (!ok) {
throw new Error(error.message);
}
console.log(data.title);
未认证更新
问题: 未授权错误。
修复: 首先认证:
await api.auth.login({ email, password });
// 或
api.updateToken(savedToken);
await api.data.updateOne("posts", 1, updates);
使用错误的关系操作符
问题: 意图添加时却替换了关系。
修复: 使用正确的操作符:
// $set替换所有关系
await api.data.updateOne("posts", 1, { tags: { $set: [5] } });
// 帖子现在只有标签5
// $add保留现有并添加新
await api.data.updateOne("posts", 1, { tags: { $add: [5] } });
// 帖子保留现有标签并添加标签5
忘记updateMany的Where子句
问题: 尝试更新所有记录而未提供where子句。
修复: 始终提供where子句:
// updateMany需要where子句
await api.data.updateMany(
"posts",
{ status: { $eq: "draft" } }, // 必需
{ status: "archived" }
);
// 要更新所有记录,使用显式条件
await api.data.updateMany(
"posts",
{ id: { $gt: 0 } }, // 匹配所有
{ reviewed: true }
);
验证
更新后,验证更改:
const { ok } = await api.data.updateOne("posts", 1, { title: "新标题" });
if (ok) {
const { data } = await api.data.readOne("posts", 1);
console.log("更新后的标题:", data.title);
}
或通过管理面板:管理面板 > 数据 > 选择实体 > 查找记录 > 验证字段。
该做和不该做
该做:
- 在使用响应数据前检查
ok - 更新前验证记录存在
- 使用
$add/$remove进行增量关系更改 - 处理唯一约束错误
- 更新受保护记录前认证
- 更新后重新验证缓存
不该做:
- 假设updateOne总是成功
- 意图添加时却使用
$set进行关系更新 - 在updateMany中不使用where子句
- 忽略验证错误
- 更新后忘记刷新UI
- 在关系操作符中使用不存在的ID
相关技能
- bknd-crud-create - 更新前创建记录
- bknd-crud-read - 获取记录以获取当前值
- bknd-crud-delete - 删除记录而非更新
- bknd-define-relationship - 设置关系以便链接
- bknd-bulk-operations - 大规模更新模式