sqlite_wrapper 是对 sqlite3 包(由 Simon Binder 提供)所提供的 SQLite3 绑定进行的一个简单封装。

该库无意达到像 driftmoor 这样的库那样丰富的功能,而是旨在使使用 SQLite 变得容易,仅抽象最少的内容,以便您可以在 Dart 和 Flutter 中享受使用这个伟大的数据库。

以下是指导该库开发的主要原则:

  • 无样板代码
  • 无生成代码
  • 响应式 API
  • 更简单的模型对象转换 API
  • 完全访问 SQL 语言特性,无需学习新的范例
  • 代码经过全面测试
  • 多平台(目前已在 iOS、Android、MacOS、Windows 上测试)- 不支持 Web
  • 没有(极端)魔法,只有一个漂亮而简单的 API

功能

此包仅提供少数几个方法,可以执行以下操作:

  • 连接到 SQLite 数据库
  • 在其上执行 SQL 语句
  • 使用编码和解码的 Map 或模型对象操作数据库
  • 通过流(streams)监听特定查询,以响应式且即时更新的方式获取结果
  • 通过提供 dbName 连接到多个数据库
  • 在数据库创建时或应迁移到新数据库模式版本时执行回调

入门

  • 安装 sqlite_wrapper
  • 安装 sqlite3_flutter_libs 以包含最新版本的 SQLite(iOS 和 MacOS 可选)或使 sqllite3 包在 Android、Windows 和 Linux 上工作(必需)。

用法

一个完整的 Flutter Todos 示例(还能是什么!)包含在 /example 文件夹中,但这里有一些关于可用方法的更多信息(还可以查看 /test 文件夹以获取更多示例)。

sqlite_wrapper 必须作为一个单例调用(该类始终返回同一个实例),因此无需创建或将其存储在变量中,SQLiteWrapper() 始终返回同一个实例。

大多数方法都接受一个 SQL 字符串、一个可选的参数列表和一个可选的表列表。

参数 可以作为 ? 包含在 SQL 查询中(SELECT * from users where id = ?)。

表列表 由库的响应式部分用于知道哪些表包含在查询中(通过 watch 命令)以及哪些表受特定 SQL 操作的影响(通过 execute 方法,该方法被所有其他简化方法调用:insertupdatedelete)。

可选的 dbName 参数在所有方法中都可用,允许同时管理不同的数据库。如果只有一个数据库,则忽略它;否则,请传递它来声明您要在哪个数据库上操作。

execute

这是在操作数据库时外部和内部调用的基本方法。

它根据执行的操作返回不同的结果:

  • insert - 返回最后插入的行 ID
  • update - 返回受影响的行数
  • delete - 返回受影响的行数
  • execute - 返回存储过程的结果(尚未测试)
  • other - 不返回任何内容
Future<dynamic>? execute(
 	String sql,
    {List<String>? tables, 
    List<Object?> params = const [],
    String dbName=defaultDBName})

query

这是从数据库获取结果的方法。

它返回一个 List 或一个单独的对象(如果 singleResult=true)。默认结果对象是 ResultSet,可以通过 Map 访问,但当传入 fromMap 方法时,它可以返回特定的对象类型。
如果 SQL 查询只包含一个返回字段,则结果就是该字段(例如,SELECT count(*) 返回一个 int)。

参数

  • fromMap - 用于将数据库返回的 ResultSet 转换为实现 fromMap 方法的特定模型对象
  • singleResult - 表示查询应只返回单个结果而不是结果数组(默认值:false)
 Future<dynamic> query(
 	String sql,
    {List<Object?> params = const [],
    FromMap? fromMap,
    bool singleResult = false,
    String dbName=defaultDBName})

watch

此方法返回一个 Stream,并在内部调用 query 方法。
它接受一个表列表,这些表会受到更改的监视(由 execute 方法操作)。

参数

  • tables - 应该监视更改的表列表(是的,可以通过分析 SQL 字符串来自动化,但正如我之前所说,没有魔法,因为如果这是一个 JOIN,而您只需要监视第一个表呢?)
  Stream watch(
  	String sql,
    {List<Object?> params = const [],
    FromMap? fromMap,
    bool singleResult = false,
    required List<String> tables,
    String dbName=defaultDBName})

insert

这是 execute 方法的语法糖,只需传递一个 Map 对象和表名,它就会在数据库中执行单个插入。

  Future<int> insert(
  	Map<String, dynamic> map, 
  	String table, {String dbName=defaultDBName})

update

工作方式与 insert 相同,但需要一个键字段列表(通常只是 id - keys: const['id'])来对单行进行更新。

Future<int> update(
	Map<String, dynamic> map, 
	String table,
    {required List<String> keys, String dbName=defaultDBName})

delete

工作方式与 insert 相同,但需要一个键字段列表(通常只是 id - keys: const['id'])来对单行执行删除。

这是从数据库获取结果的方法。

Future<int> delete(
	Map<String, dynamic> map, 
	String table,
    {required List<String> keys, String dbName=defaultDBName}) 

openDB

最后但同样重要的是,这是访问数据库应该调用的第一个方法。

参数

  • path - 数据库的完整路径和名称
  • version - 数据库模式的自定义版本(默认为 0)
  • onCreate - 一个回调,可以在数据库首次创建时用于执行代码
  • onUpdate - 一个回调,可用于执行迁移(它提供 fromVersion 和 toVersion 参数,用于决定执行哪些语句)

如果 path 等于常量 const String inMemoryDatabasePath = ':memory:';,则数据库将在内存中创建(有助于编写测试)。

该方法返回有关数据库的一些信息,例如它是否刚刚被创建以及使用的 SQLite 库版本。

  Future<DatabaseInfo> openDB(String path,       
  	{
  		int version = 0,
      OnCreate? onCreate,
   	  OnUpgrade? onUpgrade,
      dbName = defaultDBName}) {

closeDB

嗯,您知道的,它只是关闭数据库。

 void closeDB()

db

请谨慎处理:
该库暴露了内部数据库变量,以防您需要用它做其他事情……

 getDatabase({String dbName=defaultDBName}}

示例

请参阅 /example/test 文件夹以获取更多示例。

从文档文件夹打开数据库

这是唯一不可用的样板代码,因为我不想包含除了 sqlite3 库之外的任何外部库。

您应该包含 path 和 path_provider 来获取文档文件夹和系统路径分隔符。

import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';

initDB() async {
    final docDir = await getApplicationDocumentsDirectory();
    if (!await docDir.exists()) {
      await docDir.create(recursive: true);
    }
    await SQLiteWrapper().openDB(p.join(docDir.path, "todoDatabase.sqlite"));
  }

创建一个新表

const String sql = """
		CREATE TABLE IF NOT EXISTS "todos" (
          "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
          "title" varchar(255) NOT NULL,
          "done" int default 0
        );""";
        
await SQLiteWrapper().execute(sql);

一个简单的查询 - 单个结果

final Map? userMap = await sqlWrapper.query(
          "SELECT * FROM users WHERE name = 'Paperino'",
          singleResult: true);

映射到特定对象

final User user = await sqlWrapper.query("SELECT * FROM users WHERE name = ?",
    params: ["Paperino"],
    fromMap: User.fromMap,
    singleResult: true);

一个简单的查询 - 多个结果

 List<Map> users = await sqlWrapper.query("SELECT * FROM users");

映射到特定对象列表

List<User> allUsers = List<User>.from(
		await sqlWrapper.query(
          "SELECT * from users order by name DESC",
          fromMap: User.fromMap)
          );

手表

监视单个值

Stream stream = SQLiteWrapper().watch("SELECT COUNT(*) FROM users",
          singleResult: true, 
          tables: ["users"]);

监视映射对象列表

Stream userStream = SQLiteWrapper().watch("SELECT * FROM users",
          fromMap: User.fromMap, 
          tables: ["users"]);

插入/更新/删除

// insert
User user = User();
user.name = "Paperone";
user.id = await sqlWrapper.insert(user.toMap(), "users");

// update
user.name = "Pippo";
await sqlWrapper.update(user.toMap(), "users", keys: ["id"]);

// delete
res = await sqlWrapper.delete(user.toMap(), "users", keys: ["id"]);

附加信息

我首先为我的项目编写了这个库,但我认为它对其他人也很有用。

如果您有任何问题或想贡献(本着此库的精神),请联系我!
感谢

pub.dev

https://pub.dev/packages/sqlite_wrapper