新闻中心

在TypeScript中将SQLite查询结果映射到类型化对象

2025-11-14
浏览次数:
返回列表

在TypeScript中将SQLite查询结果映射到类型化对象

本教程旨在解决在electron或node.js环境中使用`sqlite3`时,如何将异步sqlite查询结果正确地反序列化并映射到typescript强类型对象的问题。文章将详细解释`sqlite3.all()`方法的异步特性,演示如何利用promise封装回调,并指导开发者使用`for...of`循环安全高效地将数据库行数据转换为自定义的typescript类实例,从而提升代码的类型安全性和可维护性。

引言:SQLite查询结果到TypeScript对象的挑战

在基于Node.js或Electron的应用程序中,使用sqlite3库与SQLite数据库交互是常见的需求。当从数据库中查询数据时,通常会得到一个原始的数据行数组。然而,为了更好地利用TypeScript的类型系统,并提高代码的可读性和健壮性,我们通常希望将这些原始数据行转换为预定义的TypeScript类或接口的实例。

直接将数据库查询结果映射到TypeScript对象可能会遇到一些挑战,特别是涉及到sqlite3库的异步操作方式以及J*aScript中循环迭代的细微差别。本教程将深入探讨这些问题,并提供一个清晰、专业的解决方案。

理解sqlite3.all()的异步特性

sqlite3库中的大多数数据库操作都是异步的,这意味着它们不会立即返回结果。db.prepare().all()方法也不例外。根据node-sqlite3的API文档,all()方法接受一个回调函数作为参数,当查询完成并获取所有结果行时,该回调函数会被调用。all()方法本身返回的是Statement对象,用于方法链式调用,而不是查询结果本身。

这意味着,如果你尝试像同步代码那样直接在query.all()之后访问结果,你将无法获取到数据,因为数据仍在异步处理中。为了正确处理这种异步行为,我们需要采用适当的异步编程模式,例如回调函数或Promise。

使用Promise封装异步操作

在现代J*aScript和TypeScript开发中,Promise是处理异步操作的首选方式,它能使异步代码的结构更清晰,避免“回调地狱”。我们可以将query.all()的基于回调的API封装在一个Promise中,使其返回一个Promise对象,这样我们就可以使用.then()或async/await来处理结果。

以下是封装GetAllObjs函数以返回一个Promise的示例:

import * as sqlite3 from 'sqlite3'; // 假设你已经配置了sqlite3

// 定义Obj接口,与数据库表结构匹配
interface Obj {
  id: number;
  name: string;
  amount: number;
}

// 假设db是一个已连接的sqlite3数据库实例
// const db = new sqlite3.Database(':memory:'); 

/**
 * 从ObjTable中获取所有对象并返回一个Promise
 * 该Promise解析为一个Obj数组
 */
export const GetAllObjs = (): Promise<Obj[]> => {
  const query = db.prepare("SELECT * FROM ObjTable");

  return new Promise((resolve, reject) => {
    query.all((err: Error | null, rows: any[]) => {
      if (err) {
        // 如果发生错误,拒绝Promise
        return reject(err);
      }

      const objs: Obj[] = [];
      // 使用 for...of 循环正确迭代行数据
      for (const row of rows) {
        // 将每一行数据映射到Obj接口
        objs.push({
          id: row.id,
          name: row.name,
          amount: row.amount,
        });
      }
      // 解析Promise,返回Obj数组
      resolve(objs);
    });
  });
};

正确迭代查询结果并构建TypeScript对象

在上述Promise封装中,一个关键的改进是使用了for (const row of rows)循环来迭代查询结果。

Tanka Tanka

具备AI长期记忆的下一代团队协作沟通工具

Tanka 146 查看详情 Tanka

避免for...in的陷阱: 原始问题中可能出现的错误是使用for (const row in rows as Obj[])。for...in循环设计用于遍历对象的可枚举属性名称(键),而不是数组的元素值。对于数组,for...in会遍历其索引(如"0", "1", "2"等字符串),而不是数组中实际的对象。因此,row变量将是一个字符串索引,而非数据库行对象,导致row.id等访问失败。

使用for...of进行迭代:for...of循环是ES6引入的,专门用于遍历可迭代对象(如数组、Map、Set等)的元素值。当对rows数组使用for...of时,每次迭代的row变量将直接是数组中的一个数据行对象。这样,你就可以安全地通过row.columnName的方式访问每个列的值,并将其赋值给TypeScript对象的相应属性。

在回调函数内部,rows参数是一个any[]类型的数组,其中每个元素都是一个表示数据库行的J*aScript对象。这些对象的属性名与数据库表的列名相匹配。因此,我们可以直接通过row.id、row.name、row.amount等方式访问这些值,并构造新的Obj实例。

完整示例代码

为了提供更完整的上下文,以下是一个包含Obj接口定义、表创建和数据获取的综合示例:

import * as sqlite3 from 'sqlite3';

// 假设db是一个已连接的sqlite3数据库实例
// 通常在Electron主进程或Node.js服务启动时初始化
const db = new sqlite3.Database('./mydb.sqlite', (err) => {
  if (err) {
    console.error("Error opening database", err);
  } else {
    console.log("Database connected.");
  }
});

// 1. 定义Obj接口,与数据库表结构匹配
interface Obj {
  id: number;
  name: string;
  amount: number;
}

// 2. 表创建函数
export const CreateObjTable = (): Promise<sqlite3.RunResult> => {
  return new Promise((resolve, reject) => {
    const query = db.prepare(`
      CREATE TABLE IF NOT EXISTS ObjTable
      (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT,
        amount INTEGER
      )
    `);
    query.run((err: Error | null, result: sqlite3.RunResult) => {
      if (err) {
        return reject(err);
      }
      resolve(result);
    });
  });
};

// 3. 插入数据示例(可选,用于测试)
export const InsertObj = (name: string, amount: number): Promise<sqlite3.RunResult> => {
  return new Promise((resolve, reject) => {
    const query = db.prepare("INSERT INTO ObjTable (name, amount) VALUES (?, ?)");
    query.run(name, amount, function(err: Error | null) {
      if (err) {
        return reject(err);
      }
      // this.lastID 可以获取插入的ID
      resolve(this); 
    });
  });
};

// 4. 获取所有对象并反序列化为Obj[]
export const GetAllObjs = (): Promise<Obj[]> => {
  const query = db.prepare("SELECT * FROM ObjTable");

  return new Promise((resolve, reject) => {
    query.all((err: Error | null, rows: any[]) => {
      if (err) {
        return reject(err);
      }

      const objs: Obj[] = [];
      for (const row of rows) { // 正确使用 for...of 迭代
        objs.push({
          id: row.id,
          name: row.name,
          amount: row.amount,
        });
      }
      resolve(objs);
    });
  });
};

// 使用示例 (在async函数中或Promise链中调用)
(async () => {
  try {
    await CreateObjTable();
    console.log("ObjTable created or already exists.");

    await InsertObj("Item A", 100);
    await InsertObj("Item B", 250);
    console.log("Sample data inserted.");

    const allObjs = await GetAllObjs();
    console.log("Retrieved objects:", allObjs);

    // 确保关闭数据库连接
    db.close((err) => {
      if (err) {
        console.error("Error closing database", err);
      } else {
        console.log("Database closed.");
      }
    });

  } catch (error) {
    console.error("An error occurred:", error);
  }
})();

注意事项与最佳实践

  1. 错误处理: 在Promise中,务必在回调函数内部检查err参数。如果存在错误,应调用reject(err)来拒绝Promise,以便上层调用者能够捕获并处理错误。
  2. 类型断言: 在query.all()的回调中,rows通常被推断为any[]。在构建objs数组时,虽然我们知道row的结构,但为了更严格的类型检查,可以使用as Obj进行类型断言,例如objs.push(row as Obj),但这要求row的类型与Obj完全兼容。在上面的例子中,我们显式地创建了一个新对象,这提供了更好的控制和清晰度。
  3. async/await: 一旦你的函数返回Promise,你就可以在调用端使用async/await语法来进一步简化异步代码的编写和阅读,使其看起来更像同步代码。
    async function processData() {
      try {
        const objs = await GetAllObjs();
        console.log(objs);
      } catch (error) {
        console.error("Failed to get objects:", error);
      }
    }
    processData();
  4. 数据库连接管理: 确保在应用程序生命周期的适当时候打开和关闭数据库连接,以避免资源泄露。在Electron应用中,这通常在主进程启动时打开,在应用关闭前关闭。
  5. 参数化查询: 在插入或更新数据时,始终使用参数化查询(如INSERT INTO ... VALUES (?, ?))来防止SQL注入攻击。

总结

通过本教程,我们学习了如何在TypeScript中优雅地处理sqlite3的异步查询结果,并将其反序列化为强类型对象。关键在于:

  • 理解sqlite3.all()的异步回调机制。
  • 使用Promise封装异步操作,以获得更清晰、可维护的代码结构。
  • 利用for...of循环正确迭代数据库查询结果数组。
  • 将每一行数据安全地映射到预定义的TypeScript接口或类实例。

遵循这些实践,将大大提高你在Electron或Node.js项目中与SQLite数据库交互时的代码质量、类型安全性和开发效率。

以上就是在TypeScript中将SQLite查询结果映射到类型化对象的详细内容,更多请关注其它相关文章!


# 都是  # 软文推广营销的方式  # 盐田谢岗网站建设  # 纺织关键词排名服务  # seo虚拟点击公司  # 驻马店营销网站推广  # 广州海珠滚屏网站建设  # 营销推广软件设计  # 青海seo服务公司  # SEO网课哪种好  # 安徽抖音营销推广方法  # 使其  # 行数  # 而不是  # 遍历  # javascript  # 如何实现  # 是一个  # 迭代  # 查询结果  # 回调  # sql注入  # ai  # 回调函数  # typescript  # node  # node.js  # js  # java  # es6 


相关栏目: 【 科技资讯46185 】 【 网络学院92790


相关推荐: CSS图片焦点样式实现教程:理解与应用tabindex属性  Yandex搜索引擎官网入口_俄罗斯Yandex免登录一键直达  b站怎么删除评论_b站评论管理与删除操作  理解J*aScript Promise的微任务队列与执行顺序  mysql如何设置表访问权限_mysql表访问权限配置  msn官网入口地址手机版 msn官方网站手机最新链接  一加 Nord 5 隐私权限异常_一加 Nord 5 系统安全优化  如何为你的Composer包编写自动化测试_集成PHPUnit到Composer的scripts工作流  深入理解rpy2中的类型转换:优化Python对象到R矩阵的映射  QQ邮箱稳定登录入口_QQ邮箱官方网站网页版使用  蛙漫官方正版入口 蛙漫网页在线全集免费观看  LINUX怎么设置定时任务_LINUX crontab配置教程  cad怎么合并重叠的线段_cad清理重复重叠线条的操作方法  夸克浏览器网页版最新地址 夸克浏览器官方入口合集  顺丰快递查询系统 官方正版查询入口  魅族17怎样用浏览器译外语网页_iPhone魅族17浏览器译外语网页【即时翻译】  LINUX的perf命令入门_LINUX官方性能分析工具的使用与解读  魅族20怎样在浏览器开无图省流_iPhone魅族20浏览器开无图省流【流量节省】  C++20的source_location是什么_C++在编译期获取源码位置信息用于日志和断言  J*a应用程序首次运行自动创建文件与目录的最佳实践  UE5.7引擎表现爆炸优化无敌!5090跑4K稳定60FPS  Win11怎么设置鼠标指针速度_Win11提高鼠标指针精确度选项  铃兰之剑为这和平的世界希里技能组及加点推荐  如何使用CaptainHook和Composer管理Git钩子_在提交前自动运行代码检查的Composer配置  Win11 BitLocker密码忘了怎么办 Win11找回BitLocker恢复密钥方法【解决】  谷歌浏览器怎么给标签页静音_Chrome标签静音快捷操作  Mac终端命令大全_Mac常用Terminal指令速查  Golang如何实现容器化日志收集与分析_Golang容器日志收集分析方法  在Typer应用中优雅地处理和重组任意命令行参数  冬*霸灯泡不亮怎么办_浴霸取暖灯一盏不亮的灯座清洁修复法  构建轻量级网站内部消息系统:Formspree 集成指南  Golang并发任务中错误如何聚合_Golang goroutine error收集方式  铁路12306官网网页端快速入口 铁路12306官方首页登录教程  在J*a中如何使用Stream.map转换元素_Stream映射操作解析  KFC套餐升级怎么获取优惠代码_KFC套餐升级活动与优惠代码获取方法  解决Python单元测试中Mock异常方法调用计数为零的问题  机构:以往存储涨价周期小米利润率实际上有所改善 能转嫁给消费者等  如何将HTML表格多行数据保存到Google Sheet  Win11如何使用Windows Sandbox Win11沙盒功能开启与使用教程【详解】  mysql通配符支持数字匹配吗_mysql通配符能否用于数字匹配的解析  AO3镜像入口大全 AO3网页版内容访问全集  聚水潭ERP登录页面入口 聚水潭ERP官网登录界面  服务端验证_j*ascript输入检查  Node.js中HTML按钮与J*aScript函数交互的正确姿势  sublime怎么覆盖插件的默认快捷键_sublime快捷键优先级与设置  汽车之家官方网站官网入口_汽车之家网页版直接进入  Golang如何实现Web接口签名验证_Golang Web接口签名校验开发方法  《北京人工智能产业白皮书(2025)》发布:全年核心产值预计突破 4500 亿元  Composer如何解决json扩展缺失的错误  蛙漫2台版漫画地址 Manwa2正版网页版链接 

搜索