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
相关产品推荐
相关产品推荐

