为啥 Flutter Floor DAO findAll 返回具有空 ID 的实体集?

Posted

技术标签:

【中文标题】为啥 Flutter Floor DAO findAll 返回具有空 ID 的实体集?【英文标题】:Why Flutter Floor DAO findAll returns entity set with null ids?为什么 Flutter Floor DAO findAll 返回具有空 ID 的实体集? 【发布时间】:2021-01-07 05:35:20 【问题描述】:

这是一个使用 Floor(相当于 Jetpack 的 Room 包)的 Flutter 项目。 我有一个具有自动递增 id 的实体 (请注意,这是前空安全代码,在答案中,我现在从后空安全 sn-p 开始)

const String ACTIVITIES_TABLE_NAME = 'activities';

@Entity(
  tableName: ACTIVITIES_TABLE_NAME,
  indices: [
    Index(value: ['start'])
  ],
)
class Activity 
  @PrimaryKey(autoGenerate: true)
  int id;
  @ColumnInfo(name: 'device_name')
  final String deviceName;
  @ColumnInfo(name: 'device_id')
  final String deviceId;
  final int start;

  Activity(
    this.deviceName,
    this.deviceId,
    this.start,
  );

我有这个 DAO:

@dao
abstract class ActivityDao 
  @Query('SELECT * FROM $ACTIVITIES_TABLE_NAME ORDER BY start DESC')
  Future<List<Activity>> findAllActivities();

  @insert
  Future<int> insertActivity(Activity activity);

到目前为止,我在我的应用中记录了五个活动,笨蛋。

_database =
    await $FloorAppDatabase.databaseBuilder('app_database.db').build();
...
    _activity =
       Activity(deviceName: device.name, deviceId: device.id.id, start: _lastRecord.millisecondsSinceEpoch);
    final id = await _database.activityDao.insertActivity(_activity);
    _activity.id = id;

然后在另一个视图中我列出了它们,但是有一个潜在的问题。

final data = await _database.activityDao.findAllActivities();

当我使用这种死的简单方法查询实体时,我的数据库中的所有五个左右的活动都返回一个 id 字段,其中填充了 null。这使得实体完全无用,因为我想对其执行的任何其他操作由于缺少实际 ID 而失败。我是不是做错了什么?

我主要有使用 mysql、Postgres 和非 SQLite RDBMS 的经验。正如我在 SQLite 中所理解的,每一行都有一个唯一的rowid,并且通过声明我的自动增量id 主键字段基本上我为那个rowid 取别名?不管是什么,我都需要身份证。不能为空。


我正在调试 Floor 的内容。

  Future<List<T>> queryList<T>(
    final String sql, 
    final List<dynamic> arguments,
    @required final T Function(Map<String, dynamic>) mapper,
  ) async 
    final rows = await _database.rawQuery(sql, arguments);
    return rows.map((row) => mapper(row)).toList();
  

此时rows 仍然正确填写了 ID,尽管行中的实体只是动态值的列表。 rows.map 应该将它们映射到实体对象,并且由于某种原因不能携带 id?这可能是地板错误吗?


好的,现在我看到在生成的database.g.dart 中,映射器没有 id:

  static final _activitiesMapper = (Map<String, dynamic> row) => Activity(
      deviceName: row['device_name'] as String,
      deviceId: row['device_id'] as String,
      start: row['start'] as int);

这就解释了为什么 id 是null。但这是生成的代码,我如何告诉 Floor 我需要 id?或者我为什么要告诉它,它必须默认存在,谁不想知道对象的主键?

【问题讨论】:

已投诉github.com/vitusortner/floor/issues/401 【参考方案1】:

好的,这是因为我没有 id 作为构造函数参数。我没有那个,因为它是一个自动增量字段,我不是决定它的人。但是,如果没有它作为构造函数参数,生成的代码不能将它与映射器一起作为参数传递,因此它会将其从映射器中排除。所以我添加了 id 作为构造函数参数。

发布空安全版本:

class Activity 
  @PrimaryKey(autoGenerate: true)
  int? id;
  @ColumnInfo(name: 'device_name')
  final String deviceName;
  @ColumnInfo(name: 'device_id')
  final String deviceId;
  final int start;
  final int end;

  Activity(
    this.id,
    required this.deviceName,
    required this.deviceId,
    required this.start,
    this.end: 0,
  );

我这里也加了这个Floor GitHub issue,看来以后可能会有注解:https://github.com/vitusortner/floor/issues/527


前零安全版本​​:

import 'package:meta/meta.dart';

class Activity 
  @PrimaryKey(autoGenerate: true)
  int id;
  @ColumnInfo(name: 'device_name')
  @required
  final String deviceName;
  @ColumnInfo(name: 'device_id')
  @required
  final String deviceId;
  final int start;

  Activity(
    this.id: null,
    this.deviceName,
    this.deviceId,
    this.start,
  );

编译器对 null 有点不确定,但在 flutter packages pub run build_runner build 之后,database.g.dart 的映射器也有 id

【讨论】:

以上是关于为啥 Flutter Floor DAO findAll 返回具有空 ID 的实体集?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Math.floor 返回一个双精度值?

为啥这个涉及 floor 函数的公式没有给出我期望的结果?

为啥 std::round 提供返回 long 和 long long 的版本,而 std::floor、std::ceil、std::trunc 不提供?

PostgreSQL 中的快速随机行:为啥 time (floor(random()*N)) + (select * from a where id = const) 比 select where i

为啥还要打扰DAO层

为啥嘲笑 DAO 时 mockito 崩溃?