Xamarin同步Realm数据库删除条目时触发NullReferenceException求助
解决Xamarin中Realm同步删除后触发NullReferenceException的问题
这个问题我之前在处理Realm同步+Xamarin控件绑定的时候也遇到过,核心原因是Realm同步模式下的实时查询集合(IQueryable<T>)和ListView的直接绑定存在异步更新冲突——你删除条目后,Realm的同步机制会异步更新本地数据集,但你手动触发的PropertyChanged会让ListView立刻尝试刷新,此时集合内部可能处于半更新状态,导致访问空引用。而移除listView.ItemsSource = Employees后,UI不再绑定这个实时集合,自然不会触发异常。
核心问题拆解
- Realm的
_realm.All<Employee>()返回的是一个实时查询集合,在同步模式下,它的更新是异步的(由Realm的同步后台线程处理)。 - 你手动调用
PropertyChanged通知UI更新时,ListView会立刻遍历这个集合,但此时Realm可能还没完成同步删除的本地状态更新,导致集合内出现空引用或者无效的元素引用。
具体解决方案
我们需要把Realm的实时查询集合转换成UI友好的ObservableCollection<T>,并通过订阅Realm的集合变更通知来同步数据,避免手动触发更新带来的冲突。
1. 修改属性类型为ObservableCollection
首先把你的Employees属性改成ObservableCollection<Employee>,这是Xamarin UI控件的标准绑定集合:
private ObservableCollection<Employee> _employees; public ObservableCollection<Employee> Employees { get => _employees; set { _employees = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Employees))); } } // 同样处理Entertainments private ObservableCollection<Entertainment> _entertainments; public ObservableCollection<Entertainment> Entertainments { get => _entertainments; set { _entertainments = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Entertainments))); } }
2. 初始化时订阅Realm集合变更
在Initialize方法中,将Realm查询结果转换成ObservableCollection,并订阅Realm的集合变更通知,自动同步UI集合:
private async Task Initialize() { _realm = await OpenRealm(); // 处理Employee集合 var employeeQuery = _realm.All<Employee>(); Employees = new ObservableCollection<Employee>(employeeQuery); // 订阅Realm集合的变更通知,自动同步到ObservableCollection var employeeSubscription = employeeQuery.SubscribeForNotifications((sender, changes, error) => { if (error != null) { // 可以在这里处理同步错误,比如弹窗提示 return; } if (changes == null) { // 首次加载完成,无需处理变更 return; } // 根据Realm的变更更新ObservableCollection,必须在主线程执行 Device.BeginInvokeOnMainThread(() => { // 倒序删除,避免索引偏移 foreach (var index in changes.DeletedIndices.OrderByDescending(i => i)) { Employees.RemoveAt(index); } // 插入新元素 foreach (var index in changes.InsertedIndices) { Employees.Insert(index, employeeQuery.ElementAt(index)); } // 更新修改的元素 foreach (var index in changes.ModifiedIndices) { Employees[index] = employeeQuery.ElementAt(index); } }); }); // 同样处理Entertainment集合 var entertainmentQuery = _realm.All<Entertainment>(); Entertainments = new ObservableCollection<Entertainment>(entertainmentQuery); var entertainmentSubscription = entertainmentQuery.SubscribeForNotifications((sender, changes, error) => { if (error != null) return; if (changes == null) return; Device.BeginInvokeOnMainThread(() => { foreach (var index in changes.DeletedIndices.OrderByDescending(i => i)) { Entertainments.RemoveAt(index); } foreach (var index in changes.InsertedIndices) { Entertainments.Insert(index, entertainmentQuery.ElementAt(index)); } foreach (var index in changes.ModifiedIndices) { Entertainments[index] = entertainmentQuery.ElementAt(index); } }); }); // 保存订阅对象,用于页面销毁时释放 _subscriptions.Add(employeeSubscription); _subscriptions.Add(entertainmentSubscription); } // 新增字段保存订阅对象 private List<IDisposable> _subscriptions = new List<IDisposable>();
3. 简化删除操作与UI更新逻辑
现在你不需要手动触发PropertyChanged了,Realm的变更通知会自动同步UI集合:
void OnDeleteClicked(object sender, EventArgs e) { try { var o = _realm.All<Employee>().FirstOrDefault(c => c.EmpId == 4); if (o != null) { _realm.Write(() => { _realm.Remove(o); }); } // 移除手动调用的PropertyChanged,由订阅的通知自动处理UI更新 } catch (Exception exp) { string msg = exp.Message; // 建议在这里添加日志或弹窗提示,方便调试 } }
4. 优化页面生命周期处理
在页面销毁时取消Realm的订阅,避免内存泄漏:
protected override void OnDisappearing() { base.OnDisappearing(); foreach (var subscription in _subscriptions) { subscription.Dispose(); } _subscriptions.Clear(); }
额外注意事项
- 所有UI更新操作必须在主线程执行,所以我们用了
Device.BeginInvokeOnMainThread来包裹集合修改逻辑。 - 异步初始化时用
_ = Initialize()避免编译器警告,也可以实现IAsyncInitialization接口来更规范地处理异步初始化。
内容的提问来源于stack exchange,提问作者maria nabil
相关产品推荐
相关产品推荐

