嵌套 FutureBuilder 与嵌套调用以从数据库延迟加载

Nested FutureBuilder vs nested calls for lazy loading from database

我需要在我可以遵循的两种方法中选择最佳方法。

我有一个使用 sqflite 保存数据的 Flutter 应用程序,在数据库中我有两个 tables:

员工:

+-------------+-----------------+------+
| employee_id | employee_name   |dep_id|
+-------------+-----------------+------+
|     e12     | Ada Lovelace    | dep1 |
+-------------+-----------------+------+
|     e22     | Albert Einstein | dep2 |
+-------------+-----------------+------+
|     e82     | Grace Hopper    | dep3 |
+-------------+-----------------+------+

SQL:

CREATE TABLE Employee(
    employee_id TEXT NOT NULL PRIMARY KEY,
    employee_name TEXT NOT NULL ,
    dep_id TEXT,
    FOREIGN KEY(dep_id) REFERENCES Department(dep_id)
    ON DELETE SET NULL
);

部门:

+--------+-----------+-------+
| dep_id | dep_title |dep_num|
+--------+-----------+-------+
| dep1   | Math      | dep1  |
+--------+-----------+-------+
| dep2   | Physics   | dep2  |
+--------+-----------+-------+
| dep3   | Computer  | dep3  |
+--------+-----------+-------+

SQL:

CREATE TABLE Department(
    dep_id TEXT NOT NULL PRIMARY KEY,
    dep_title TEXT NOT NULL ,
    dep_num INTEGER,
);

我需要显示存储在 Employee table 中的 ListGrid 个部门。我应该查看 Employee table 并从中获取部门 ID,这很容易,但是在获取 dep_id 之后我需要从这些 ID 制作一张卡片,所以我需要 部门 table 的信息。 我从 Emplyee table 获取的那些 ID 的完整信息在 Department table.

There are thousands of rows in each table.

我有一个数据库助手 class 来连接到数据库 :

DbHelper 是这样的:

Future<List<String>> getDepartmentIds() async{
    'fetch all dep_id from Employee table'
}

Future<Department> getDepartment(String id) async{
    'fetch Department from Department table for a specific id'
}

Future<List<Department>> getEmployeeDepartments() async{
    '''1.fetch all dep_id from Employee table
    2.for each id fetch Department records from Department table'''

    var ids = await getDepartmentIds();
    List<Departments> deps=[];
    ids.forEach((map) async {
        deps.add(await getDepartment(map['dep_id']));
      });
}

有两种方法:

第一个:

  1. 在 dbhelper 中定义一个函数 returns 所有 dep_id 来自 Employee table(getDepartmentIds另一个函数是 returns 该特定 id 的部门对象(模型)。(getDepartment)

  2. 现在我需要两个 FutureBuilder 彼此内部,一个用于获取 ID,另一个用于获取部门模型。

第二个:

  1. 定义一个函数,该函数首先获取 ID,然后在该函数内部将每个 ID 映射到部门模型。(getEmployeeDepartments)
  2. 所以我需要一个 FutureBuilder .

哪个更好?? 我应该让 FutureBuilders 处理它还是我应该向 dbHelper 施加压力来处理它?

如果我使用第一种方法,那么我必须(据我所知!)进行第二次未来调用(获取 Department Object(model)基于它在 build 函数上的 id(getDepartment)),建议不要这样做。

第二个的问题是它在 dbHelper 中做了很多嵌套调用。

我用 ListView.builder 来提高性能。

我用一些数据检查了两者,但无法确定哪个更好。我想这取决于 flutter 和 sqlite(sqflite)。

哪个更好或者有没有更好的方法?

鉴于我没有在这个示例中看到太多代码,我将对您的问题进行 high-level 回答。

评估方法一

  1. 马上这部分就突出了:"returns all dep_id from Employee table"
  2. 我会说从头开始,因为 "return all" 通常从来都不是一个好的解决方案,特别是因为你提到你的 table 有很多行。

评估方法二

  1. 我不确定这与第一种方法相比在性能上有何不同,出于同样的原因似乎也很糟糕。我认为这只是大大改变了您的 UI 逻辑。

典型'Endless'列表方法

  1. 您可以查询 Employees table 并加入 Departments table。
  2. 您将在 UI 上实施分页,并将您的值传递给第一步中的查询。
  3. 在基本级别,您将需要这些变量:Take、Skip、HasMore
  4. 取:每次查询请求的项目数
  5. 跳过: 下一次查询要跳过的项目数,这将是您当前在内存列表中的项目数的大小驱动您的 UI.
  6. HasMore: 您可以在每个查询的响应中设置此项,让 UI 知道是否还有更多项目。
  7. 当您向下滚动列表时,当您到达底部时,您将请求更多项目。

最初发出一个查询例如:Take: 10, Skip: 0 当您到达 UI 底部时的下一个查询:Take:10,Skip:10 等..

示例sql查询:

SELECT * 
FROM Employees E
   JOIN Departments D on D.id = E.dept_id
order by E.employee_name  
offset {SKIP#} rows
FETCH NEXT {TAKE#} rows only

希望这对您有所帮助,我不完全确定您实际上在尝试做什么 - 就代码而言。

据我所知,您想要做的是获取包含部门相关信息的员工列表。

如果是这样,那就是为 INNER JOIN 量身定做。像这样:

SELECT Employee.*, Department.dep_id, Department.dep_title 
   FROM Employee INNER JOIN Department 
   ON Employee.dep_id = Department.dep_id;

(虽然你可能想仔细检查一下,我的 SQL 有点生疏了)。

这将一步完成您所需要的。但是,仍然存在您要问的问题,这似乎是 "Is it more efficient to do many small requests or one big one, and what are the performance ramifications".

这个问题的答案有点特定于 Flutter。当您使用 SQFLITE 发出请求时发生的事情是,它正在处理您传递给它的任何内容,将其发送到 java/objc 并可能进行更多处理并将处理推送到后台线程,然后调用 SQLITE库做更多的理解请求的处理,然后实际读取磁盘上的数据做操作,然后returns回到java/objc层,将响应推送到UI 个线程,依次响应 dart。

如果这听起来不是特别有效,那是因为它不是 =D。如果你这样做几次(甚至几百次)可能没问题,但如果你像你说的那样进入数千次,它可能会开始变慢。

您提出的备选方案是提出一个大型请求。你会比我更清楚这是否明智;如果它是几千但只有几千,并且你返回的数据总是相对较小(即只有 10-20 个字符的名称和部门名称),那么你会说 (20+20 )*2000 = 8000b = 80kb 数据。即使您假设开销会增加一倍,160 kb 的数据也不足以让任何相对较新的智能手机感到困惑(毕竟它比任何单张照片都小得多!)。

现在,利用一些特定领域的知识,您可以对此进行优化。例如,如果您知道部门的数量比员工少得多(即 < 100 或其他),您可以跳过整个连接问题,并在开始之前简单地请求所有部门并将其放入映射中(dep_id => dep_title),然后一旦你请求了员工,你就可以自己简单地从 dep_id 到 dep_title 进行查找。这样您的请求就不必一遍又一遍地包含 dep_title。

话虽这么说,但无论是否使用联接,您都可能需要考虑对员工查找进行分页。您可以通过一次请求 100 名员工(或任何数量)而不是整个批次来执行此操作 - 这样您就不会通过堆栈进行 1000 多次调用的开销,但您也没有大块内存中的所有数据。

SELECT * FROM Employee
WHERE employee_name >= LastValue
ORDER BY employee_name
LIMIT 100;

不幸的是,这与 flutter 列表的处理方式不太吻合,因此您可能需要像 'EmployeeDatabaseManager' 这样的东西来执行实际请求,并且您的列表会调入它以获取数据。不过,这可能超出了这个问题的范围。