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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:26:00