BkndCRUD更新Skill bknd-crud-update

此技能用于在Bknd平台中通过SDK或REST API执行数据库记录的更新操作。涵盖单个记录更新、批量更新、关系更新等功能,适用于后端开发和数据管理场景。关键词:Bknd, CRUD, 更新, SDK, REST API, 数据库, 后端开发, 低代码开发。

低代码开发 0 次安装 0 次浏览 更新于 3/9/2026

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 - 大规模更新模式