首页 > web前端 > js教程 > 正文

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

心靈之曲
发布: 2025-11-14 12:53:39
原创
243人浏览过

在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库的异步操作方式以及JavaScript中循环迭代的细微差别。本教程将深入探讨这些问题,并提供一个清晰、专业的解决方案。

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

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

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

使用Promise封装异步操作

在现代JavaScript和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)循环来迭代查询结果。

天工大模型
天工大模型

中国首个对标ChatGPT的双千亿级大语言模型

天工大模型 115
查看详情 天工大模型

避免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[]类型的数组,其中每个元素都是一个表示数据库行的JavaScript对象。这些对象的属性名与数据库表的列名相匹配。因此,我们可以直接通过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查询结果映射到类型化对象的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
热门推荐
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号