You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中Realm非托管对象问题:分层架构下解决方案咨询

问题本质

你通过fromDomain创建的ShoppingListItemData是全新的非托管对象,和Realm中原本存储的托管对象没有关联,因此Realm不允许对它执行修改/删除操作。核心解决思路是:始终操作Realm中已存在的托管对象,同时保持领域层、UI层完全隔离Realm依赖。

实用解决方案

方案一:数据层通过主键获取托管对象后更新(最直接)

在数据层的Repository类中,更新/删除操作不直接使用转换后的非托管对象,而是通过领域模型的主键(uuid)从Realm中查询对应的托管对象,再将领域模型的属性赋值给该托管对象,最后提交事务。

代码示例

class ShoppingListRepository {
  final Realm _realm;

  ShoppingListRepository(this._realm);

  // 更新领域模型对应的Realm托管对象
  void updateShoppingListItem(ShoppingListItem domainItem) {
    _realm.write(() {
      // 通过主键uuid查找Realm中的托管对象
      final managedItem = _realm.find<ShoppingListItemData>(domainItem.uuid);
      if (managedItem != null) {
        // 将领域模型的属性同步到托管对象
        managedItem.amount = domainItem.amount;
        managedItem.done = domainItem.done;
        
        // 处理嵌入式对象Grocery的更新
        if (managedItem.grocery == null) {
          managedItem.grocery = GroceryData(
            barcode: domainItem.grocery.barcode,
            name: domainItem.grocery.name,
            brand: domainItem.grocery.brand,
          );
        } else {
          managedItem.grocery!.barcode = domainItem.grocery.barcode;
          managedItem.grocery!.name = domainItem.grocery.name;
          managedItem.grocery!.brand = domainItem.grocery.brand;
        }
      }
    });
  }

  // 通过领域模型的主键删除托管对象
  void deleteShoppingListItem(ShoppingListItem domainItem) {
    _realm.write(() {
      final managedItem = _realm.find<ShoppingListItemData>(domainItem.uuid);
      if (managedItem != null) {
        _realm.delete(managedItem);
      }
    });
  }
}

这个方案无需修改现有领域模型和转换逻辑,UI层依然只依赖领域模型,完全隔离Realm细节。

方案二:优化转换方法,支持更新托管对象

修改转换类Interface,新增一个专门用于更新托管对象的方法,直接将领域模型的属性赋值给已有的托管对象,而非创建新对象。

代码示例

class Interface {
  // 原有的领域模型转换逻辑不变
  static ShoppingListItem toDomain(ShoppingListItemData shoppingListItemData) {
    return ShoppingListItem(
        uuid: shoppingListItemData.uuid,
        amount: shoppingListItemData.amount ?? 1,
        done: shoppingListItemData.done ?? false,
        grocery: Grocery(
            barcode: shoppingListItemData.grocery?.barcode ?? 0,
            name: shoppingListItemData.grocery?.name,
            brand: shoppingListItemData.grocery?.brand));
  }

  // 新增:用领域模型更新已有的托管对象
  static void updateManagedFromDomain(
      ShoppingListItemData managedItem, ShoppingListItem domainItem) {
    managedItem.amount = domainItem.amount;
    managedItem.done = domainItem.done;
    
    if (managedItem.grocery == null) {
      managedItem.grocery = GroceryData(
        barcode: domainItem.grocery.barcode,
        name: domainItem.grocery.name,
        brand: domainItem.grocery.brand,
      );
    } else {
      managedItem.grocery!.barcode = domainItem.grocery.barcode;
      managedItem.grocery!.name = domainItem.grocery.name;
      managedItem.grocery!.brand = domainItem.grocery.brand;
    }
  }

  // 原有的fromDomain仅用于创建新对象(插入操作)
  static ShoppingListItemData fromDomain(ShoppingListItem shoppingListItem) {
    return ShoppingListItemData(shoppingListItem.uuid,
        amount: shoppingListItem.amount,
        done: shoppingListItem.done,
        grocery: GroceryData(
            barcode: shoppingListItem.grocery.barcode,
            brand: shoppingListItem.grocery.brand,
            name: shoppingListItem.grocery.name));
  }
}

然后在Repository中调用该方法:

void updateShoppingListItem(ShoppingListItem domainItem) {
  _realm.write(() {
    final managedItem = _realm.find<ShoppingListItemData>(domainItem.uuid);
    if (managedItem != null) {
      Interface.updateManagedFromDomain(managedItem, domainItem);
    }
  });
}

这个方案把属性同步逻辑抽离到转换类中,让Repository代码更简洁,同样保持分层隔离。

方案三:领域模型携带托管对象引用(不推荐)

如果需要更直接的关联,可以在领域模型中添加一个仅数据层使用的托管对象引用字段,但会轻微破坏领域层的纯粹性,仅作为备选:

class ShoppingListItem {
  String uuid;
  int amount;
  bool done;
  Grocery grocery;
  // 仅数据层使用,UI层忽略该字段
  ShoppingListItemData? managedReference;

  ShoppingListItem({
    required this.uuid,
    required this.amount,
    required this.done,
    required this.grocery,
    this.managedReference,
  });
}

转换时传入托管对象引用:

static ShoppingListItem toDomain(ShoppingListItemData shoppingListItemData) {
  return ShoppingListItem(
      uuid: shoppingListItemData.uuid,
      amount: shoppingListItemData.amount ?? 1,
      done: shoppingListItemData.done ?? false,
      grocery: Grocery(
          barcode: shoppingListItemData.grocery?.barcode ?? 0,
          name: shoppingListItemData.grocery?.name,
          brand: shoppingListItemData.grocery?.brand),
      managedReference: shoppingListItemData);
}

更新时直接使用该引用:

void updateShoppingListItem(ShoppingListItem domainItem) {
  if (domainItem.managedReference != null && domainItem.managedReference!.isManaged) {
    _realm.write(() {
      Interface.updateManagedFromDomain(domainItem.managedReference!, domainItem);
    });
  }
}
总结

优先选择方案一或方案二,它们既保持了分层架构的清晰性,又彻底解决了非托管对象无法操作数据库的问题,完全满足UI层无Realm依赖的需求。

内容的提问来源于stack exchange,提问作者Konst

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 14:45:24