[官网文档翻译]Flutter持久化库drift -示例 - 多对多关系

11,429 阅读4分钟

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

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

本文翻译自 drift 的 官方文档 Many to many relationships (simonbinder.eu)

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


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

多对多关系

用 drift 对购物车建模的一个示例

定义Model(模型)

在本例中,我们要对一个购物系统建模,其中一些查询是在 drift 中。 首先,我们需要存储一些可购买的物品。

class BuyableItems extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get description => text()();
  IntColumn get price => integer()();
  // 可根据需要添加更多列
}

我们要定义为购物车定义两个表:一个是购物车本身,另外一个是购物车中的物品。

class ShoppingCarts extends Table {
  IntColumn get id => integer().autoIncrement()();
  // 创建购物车时也可以存储更多的信息。
}

@DataClassName('ShoppingCartEntry')
class ShoppingCartEntries extends Table {
  // 购物车就该包含 id 这个项目。
  IntColumn get shoppingCart => integer()();
  // id of the item in this cart
  // 在此购物车中的物品的 id
  IntColumn get item => integer()();
  // 可以存储一些附加信息,如添加物品的数量。
}

drift 会为这三个表生成匹配的类。但是在应用中为购物车建模要使用三个类会很让人烦恼。让我们写一个简单的类来代表整个购物车:

/// 代表一个包含装在其中所有物品的完整的购物车
class CartWithItems {
  final ShoppingCart cart;
  final List<BuyableItem> items;

  CartWithItems(this.cart, this.items);
}

插入

我们想向数据库中写一个 CartWithItems 实例。我们假定所有的 BuyableItem 已经在数据库中存在(我们需要通过 into(buyableItems).insert(BuyableItemsCompanion(...)) 来存储它们)。然后,我们可以如下插入整个购物车:

Future<void> writeShoppingCart(CartWithItems entry) {
  return transaction((_) async {
    final cart = entry.cart;

    // 首先,我们写入 购物车
    await into(shoppingCarts).insert(cart, mode: InsertMode.replace);

    // 然后我们替换购物车中的所有物品,所以要先删除原有的
    await (delete(shoppingCartEntries)
        ..where((entry) => entry.shoppingCart.equals(cart.id)))
        .go();

    // 然后写入新的物品
    for (final item in entry.items) {
      await into(shoppingCartEntries).insert(ShoppingCartEntry(shoppingCart: cart.id, item: item.id));
    }
  });
}

我们也可以定义一个辅助方法用来创建一个新的空购物车:

Future<CartWithItems> createEmptyCart() async {
  final id = await into(shoppingCarts).insert(const ShoppingCartsCompanion());
  final cart = ShoppingCart(id: id);
    // 我们将 items 属性设置为 [],因为我们刚刚创建了购物车,它还是空的。
  return CartWithItems(cart, []);
}

选择一个购物车

因为包含多个组件的 CartWithItems 类在数据库中是相互分离的(购物车的信息,添加的物品的信息),所以我们需要合并两个流。 rxdart 库提供 combineLatest2 方法在会有帮助,它允许我们:

Stream<CartWithItems> watchCart(int id) {
 // 加载购物车的信息
 final cartQuery = select(shoppingCarts)..where((cart) => cart.id.equals(id));

 // 加载购物车中物品的信息
 final contentQuery = select(shoppingCartEntries).join(
   [
     innerJoin(
       buyableItems,
       buyableItems.id.equalsExp(shoppingCartEntries.item),
     ),
   ],
 )..where(shoppingCartEntries.shoppingCart.equals(id));

 final cartStream = cartQuery.watchSingle();

 final contentStream = contentQuery.watch().map((rows) {
   // 合并带 buyableItems 的 shoppingCartEntries。
   // 这里我们只关注物品。
   return rows.map((row) => row.readTable(buyableItems)).toList();
 });

 // 现在,我们可以把两个查询合并到一个流里。
 return Rx.combineLatest2(cartStream, contentStream,
     (ShoppingCart cart, List<BuyableItem> items) {
   return CartWithItems(cart, items);
 });
}

选择所有的购物车

现在我们要监视所有的购物车和每个购物车里的物品,而不是单个购物车和与其关联的物品。对于这类变换,RxDart's switchMap 就派上用场了:

Stream<List<CartWithItems>> watchAllCarts() {
  // 启动对所有购物车的监视
  final cartStream = select(shoppingCarts).watch();

  return cartStream.switchMap((carts) {
    // 无论何时,购物车发生变动,这个方法都会被调用。
    // 对于每个购物车,现在我们想加载所有的物品到里面。
    // (这里我们创建一个购物车 id 的 Map 只是为了性能原因)
    final idToCart = {for (var cart in carts) cart.id: cart};
    final ids = idToCart.keys;

    // 选择在任何发现的购物车中的所有物品
    final entryQuery = select(shoppingCartEntries).join(
      [
        innerJoin(
          buyableItems,
          buyableItems.id.equalsExp(shoppingCartEntries.item),
        )
      ],
    )..where(shoppingCartEntries.shoppingCart.isIn(ids));

    return entryQuery.watch().map((rows) {
      // 为每一个购物车存储物品的列表,为了更快的查询,再次使用了 Map。
      final idToItems = <int, List<BuyableItem>>{};

      // 对于一个购物车中包含的每一个物品(数据行),把它们放到物品的 Map 中。
      for (var row in rows) {
        final item = row.readTable(buyableItems);
        final id = row.readTable(shoppingCartEntries).shoppingCart;

        idToItems.putIfAbsent(id, () => []).add(item);
      }

      // 最终,只剩下合并带着所有物品 Map 的购物车的 Map。
      return [
        for (var id in ids)
          CartWithItems(idToCart[id], idToItems[id] ?? []),
      ];
    });
  });
}