[官网文档翻译]Flutter持久化库drift - 使用sql - 自定义查询

11,504 阅读3分钟

「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」。

Flutter持久化库drift(原moor)官方文档翻译汇总 - 掘金 (juejin.cn)

本文翻译自 drift 的 官方文档 Custom queries (simonbinder.eu)

肉翻多有不足,不吝赐教。


重要通知: moor 已改名为 drift 。更多信息[中文]。

自定义查询

让 drift 从 sql 语句生成 dart 代码。

尽管 drift 包括了一个使用流畅的 api 可以用来为大部分语句建模,但是高级特性如 WITH 子句或子查询还没被支持。可以用自定义语句来实现这些特性。当然不要忽略 drift 提供的其它便利:drift 会帮助解析结果数据行、自定义查询也支持自动更新的流。

带有生成的 api 的语句

可以指示 drift 为选择、更新、删除语句自动生成一个类型安全的 api。当然也可以手写自定义的 sql 。细节看一下下面的内容。

要使用这个特性,所有需要做的就是在 DriftDatabase 注解中定义查询:

@DriftDatabase(
  tables: [Todos, Categories],
  queries: {
    'categoriesWithCount':
        'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" FROM categories c;'
  },
)
class MyDatabase extends _$MyDatabase {
  // 其余部分保持不变
}

再次运行构建步骤之后, drift 会写入一个 CategoriesWithCountResult - 它会保有查询的结果。 从 _$MyDatabase 类继承的类也会带有 categoriesWithCount (只运行一次查询) 方法和 watchCategoriesWithCount (返回一个自动更新的流)方法。

查询可以使用 ?:name 语法来带参数。当查询包含参数时, drift 会推断出它们适当的类型,然后把它们包含进生成的方法中。例如:'categoryById': 'SELECT * FROM categories WHERE id = :id' 会生成 categoryById(int id) 方法。

对于表名

用这个特性,会帮助了解 Dart 表在 sql 中如何命名。对于没有覆写 tableName 的表,在 sql 中的全称为类名的蛇形命名(下划线连接小写单词)。 所以一个 Categories 的 Dart 表会命名为 categories ,一个 UserAddressInformation 的 Dart 表会命名为 user_address_information 。 同样的规则也适用于没有明确指定名称的列。 drift 文件中的表和列总是会使用指定的名称。

也可以使用 UDPATEDELETE 语句。当然,这个特性对于 DAO 也可用,它也通过分析读写的表完美集成了自动更新流。

自定义选择(select)语句

如果不想用生成的 api 来调用语句,也可以通过调用单次查询的 customSelect 或查询流的 customSelectStream 发送自定义查询。 查询流会在底层数据改变时自动生成新的项目集合。 通过[开始指南]的 Todo 示例,可以写一个加载每个 cateory 的所有 todo 的数量的查询。

class CategoryWithCount {
  final Category category;
  final int count; // 当前 category 的实体数

  CategoryWithCount(this.category, this.count);
}

// 然后在数据库类里
Stream<List<CategoryWithCount>> categoriesWithCount() {
    // 选项所有的 category ,然后加载每个 category 有多少关联的实体。
    return customSelect(
      'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" FROM categories c;',
      readsFrom: {todos, categories}, // used for the stream: the stream will update when either table changes
      ).watch().map((rows) {
        // 这里有一个数据行的列表。只需要将数据行的原始数据转换为 CategoryWithCount。
        // 因为这之前已经定义了 Category 表,drift 知道如何解析一个 category。
        // 所以只剩下手动提取数量。
        return rows
          .map((row) => CategoryWithCount(Category.fromData(row.data, this), row.readInt('amount')))
          .toList();
    });
  }

对于自定义查询,应该使用 readsFrom 参数指定在读取的表。使用 Stream 时, drift 会在流之后监听到,然后更新要产出项目的流。

可以使用问号占位符和 variables 参数来绑定 sql 变量:

Stream<int> amountOfTodosInCategory(int id) {
  return customSelect(
    'SELECT COUNT(*) AS c FROM todos WHERE category = ?',
    variables: [Variable.withInt(id)],
    readsFrom: {todos},
  ).map((row) => row.readInt('c')).watch();
}

当然,也可以使用带下标的变量(例如: ?12 ) - 关于这些的更多信息,参考 sqlite3 文档

自定义更新语句

对于更新和删除语句,可以使用 customUpdate。就像 customSelectcustomUpdate 方法带有一个 sql 语句参数和一个可选的变量参数。也可以告诉 drift 在查询中使用的可选的 updates 参数会影响到哪些表。这会帮助在之后需要自动更新的其它选择语句。