[官文翻译]Futter超快数据库Isar - 概念 - 查询(上)- 过滤(Filter)

358 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情


Isar:用于 Futter 可跨平台的超快数据库

官方文档:Home | Isar Database

pub:isar | Dart Package (flutter-io.cn)

本文翻译自:Queries | Isar Database

译时版本: 3.0.2


查询

查询是如何查找匹配确定条件的记录,例如:

  • 查找所有标星的联系人
  • 查找联系人中不同的名
  • 删除所有未定义姓的联系人

因为查询是在数据库上执行,不是在 Dart 中执行,所以它们相当快。当你灵活运用索引时,可以更进一步改善查询性能。以下的内容,你会学习到如何编写查询和如何使更高效。

有两种方法可以过滤记录:过滤和 Where 子句。先看一下过滤如何工作。

过滤

过滤易于使用和理解。根据属性的类型,它们具有不同的过滤操作可用,大部分都有可自解释的名称。

过滤通过为过滤目标集合中的每个对象评价表达式来工作。如果表达式的解答是 true ,Isar 会在结果中包含对象。过滤对结果排序没有影响。

我们将使用下面的模型用于后面的示例:

@Collection()
class Shoe {
  int? id;

  int? size;

  late String model;

  late bool isUnisex;
}

查询条件

根据字段的类型,有不同的条件可用。

条件说明
.equalTo(value)匹配等于指定 value 的值
.between(lower, upper)匹配 lower 和 upper 之间的值
.greaterThan(bound)匹配大于 bound 的值
.lessThan(bound)匹配小于 bound 的值。 null 值会默认包含,因为 null 被认为小于任何值
.isNull()匹配 null 的值
.isNotNull()匹配所有非 null 的值。
.length()列表、字符串和链接长度查询,过滤列表或链接中的元素个数。

让我们假定数据库包含 4 双鞋子,它们的尺码是 39、40、46 和 未设定(null)尺码。除非你进行排序,值会返回按 id 排序后的结果。


isar.shoes.filter()
  .sizeLessThan(40)
  .findAll() // -> [39, null]

isar.shoes.filter()
  .sizeLessThan(40, include: true)
  .findAll() // -> [39, null, 40]

isar.shoes.filter()
  .sizeBetween(39, 46, includeLower: false)
  .findAll() // -> [40, 46]

逻辑操作符

可以使用以下的逻辑操作符组合操作逻辑。

操作符说明
.and()如果左右表达式的结果都为 true ,则评估结果为 true
.or()如果左右表达式的其中一个结果为 true ,则评估结果为 true
.xor()如果只有一个表达式的结果为 true 则评估结果为 true
.not()之后的表达式结果取反。
.group()对条件进行分组和允许指定评估顺序。

如果想要查找所有尺码为 46 的鞋,可以使用下面的查询:

final result = await isar.shoes.filter()
  .sizeEqualTo(46)
  .findAll();

如果使用多于一个条件,可以用逻辑 .and() 、逻辑 .or() 、逻辑异或 .xor() 绑定多个过滤:

final result = await isar.shoes.filter()
  .sizeEqualTo(46)
  .and() // 可选。Filter隐式用逻辑与绑定。
  .isUnisexEqualTo(true)
  .findAll();

该查询等同于: size == 46 && isUnisex == true

也可以使用 .group() 对条件分组:

final result = await isar.shoes.filter()
  .sizeBetween(43, 46)
  .and()
  .group((q) => q
    .modelNameContains('Nike')
    .or()
    .isUnisexEqualTo(false)
  )
  .findAll()

该查询等同于 size >= 43 && size <= 46 && (modelName.contains('Nike') || isUnisex == false)

要对一个条件或分组取反,使用逻辑取反 .not()

final result = await isar.shoes.filter()
  .not().sizeEqualTo(46)
  .and()
  .not().isUnisexEqualTo(true)
  .findAll();

该查询等同于 size != 46 && isUnisex != true

字符串条件

可以使用字符串操作符比较字符串的值。正则类的通配符在查询时有更多的弹性。

条件说明
.startsWith(value)匹配以指定的 value 开始的值。
.contains(value)匹配包含指定的 value 的值。
.endsWith(value)匹配以指定的 value 结束的值。
.matches(wildcard)匹配符合 wildcard (通配符)模式的值。

大小写敏感
所有的字符串操作都有一个可选的 caseSensitive 参数,默认是 true(即默认大小写敏感)。

通配符:
通配符表达式 是一个字符串,它使用带有两个特殊通配符的普通字符:

  • * 通配符匹配 0 个或多个任意字符
  • ? 通配符匹配任意字符。例如,通配符字符串 "d?g" 匹配 "dog"、 "dig"、 和 "dug"、 但不匹配 "ding"、 "dg" 或 "a dog"

查询修饰符

有时候,基本一些条件或不同的值构建查询是必需的。Isar 带有非常强大的工具来构建条件式查询:

修饰符说明
.optional(cond, qb)只在cond 是 true 时扩展查询。该修饰符几乎可用于查询的任何地方,例如根据条件排序或限定。
.anyOf(list, qb)values 中的每个值扩展查询,并使用逻辑绑定条件。
.allOf(list, qb)values 中的每个值扩展查询,并使用逻辑绑定条件。

本示例中,我们构建了一个带可选过滤的查找鞋子的方法:

Future<List<Shoe>> findShoes(Id? sizeFilter) {
  return isar.shoes.filter()
    .optional(
      sizeFilter != null, // 只在 sizeFilter != null 时进行过滤。
      (q) => q.sizeEqualTo(sizeFilter!),
    ).findAll();
}

如果要查找具有多种鞋码之一的所有鞋子,可以编写常规查询或使用 anyOf() 修饰符:

final shoes1 = await isar.shoes.filter()
  .sizeEqualTo(38)
  .or()
  .sizeEqualTo(40)
  .or()
  .sizeEqualTo(42)
  .findAll();

final shoes2 = await isar.shoes.filter()
  .anyOf(
    [38, 40, 42],
    (q, int size) => q.sizeEqualTo(size)
  ).findAll();

// shoes1 == shoes2

想要构建动态查询时,查询修饰符特别有用。

列表

列表也能用在查询中使用。

class Tweet {
  Id? id;

  String? text;

  List<String> hashtags = [];
}

可以基于列表长度查询:

final tweetsWithoutHashtags = await isar.tweets.filter()
  .hashtagsIsEmpty()
  .findAll();

final tweetsWithManyHashtags = await isar.tweets.filter()
  .hashtagsLengthGreaterThan(5)
  .findAll();

这些等于 Dart 代码的 tweets.where((t) => t.hashtags.isEmpty); 和 tweets.where((t) => t.hashtags.length > 5);

也可以基于列表元素查询:

final flutterTweets = await isar.tweets.filter()
  .hashtagsElementEqualTo('flutter')
  .findAll();

这等于 Dart 代码 tweets.where((t) => t.hashtags.contains('flutter'));

嵌入对象

嵌入对象可使用相同的条件作为顶级对象来查询。 我们假定有下面的模型:

@collection
class Car {
  Id? id;

  Brand? brand;
}

@embedded
class Brand {
  String? name;

  String? country;
}

我们想查询所有品牌是 "BMW" 和 国家是 "Germany" 的汽车。 我们可以使用如下查询去做:

final germanCars = await isar.cars.filter()
  .brand((q) => q
    .nameEqualTo('BMW')
    .and()
    .countryEqualTo('Germany')
  ).findAll();

总要试着分组嵌套查询。 即使结果相同,上面的查询也比下面的查询更高效:

final germanCars = await isar.cars.filter()
  .brand((q) => q.nameEqualTo('BMW'))
  .and()
  .brand((q) => q.countryEqualTo('Germany'))
  .findAll();

链接

如果模型包含链接或反向链接 ,会基于链接对象过滤查询。

@Collection()
class Teacher {
    int? id;

    late String subject;
}

@Collection()
class Student {
    int? id;

    late String name;

    final teachers = IsarLinks<Teacher>();
}

可以举个例子,查找有数学老师或英语老师的学生:

final result = await isar.students.filter()
  .teachers((q) => q.subjectEqualTo('Math')
    .or()
    .subjectEqualTo('English')
  ).findAll()

如果至少有一个链接对象匹配条件,链接过滤的评价结果会为 true

我们查询一下所有没有老师的学生:

final result = await isar.students.filter().teachersLengthEqualTo(0).findAll();

或者下面的代替方案:

final result = await isar.students.filter().teachersIsEmpty().findAll();