WPF列表控件绑定Realm-dotnet实时查询删除异常求助
WPF中Realm DB删除对象后UI不自动刷新的问题解决办法
我正尝试在WPF应用中将LiteDB替换为Realm DB,目标是让ListBox和DataGrid绑定到视图模型的实时查询,确保数据库操作后UI能同步更新状态。
添加对象时UI能正常更新,因为RealmResults是可观察的,但删除对象时,列表控件不会自动刷新,还会抛出**"尝试访问无效对象"**的异常。把示例适配到WinUI 3后删除操作正常,推测是WPF和realm-dotnet的兼容性问题。除了每次删除后重新赋值ItemsSource,有没有其他解决办法?
复现代码
MainWindowViewModel.cs
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using MongoDB.Bson; using Realms; using Realms.Exceptions; namespace RealmDb { public partial class MainWindowViewModel : ObservableObject { public MainWindowViewModel() { var config = new RealmConfiguration(@"d:\temp\cats.realm"); try { Realm.DeleteRealm(config); } catch(Exception ex){ } LocalRealm = Realm.GetInstance(config); Cats = LocalRealm.All<Cat>(); } public Realm LocalRealm; private IQueryable<Cat> _cats; public IQueryable<Cat> Cats { get => _cats; set => SetProperty(ref _cats, value); } public Cat? SelectedCat { get; set; } [RelayCommand] private void RemoveCat() { if (SelectedCat == null) return; try { // 抛出异常:尝试访问无效对象 LocalRealm.Write(() => { LocalRealm.Remove(SelectedCat); }); /* 临时解决办法: Cats = null; Cats = LocalRealm.All<Cat>();*/ } catch (Exception ex) { // 选中的猫未从DataGrid中移除。 // 若绑定到ListBox,已删除对象仍会显示,但选中时会抛出异常。 MessageBox.Show($@"删除选中猫时出错。{ex.Message}"); } } [RelayCommand] private void Populate() { Cat ninja = new() { Name = "Ninja", Age = 1, Breed = "Angora" }; Cat nounou = new() { Name = "Nounou", Age = 2, Breed = "Siamese" }; Cat leila = new() { Name = "Layla", Age = 3, Breed = "Local" }; try { // 批量写入抛出"Range actions are not supported"(不支持批量操作) LocalRealm.Write(() => { LocalRealm.Add(nounou); }); LocalRealm.Write(() => { LocalRealm.Add(ninja); }); LocalRealm.Write(() => { LocalRealm.Add(leila); }); } catch (RealmFileAccessErrorException ex) { MessageBox.Show($@"写入Realm文件时出错。{ex.Message}"); } } } }
MainWindow.xaml
<Window x:Class="RealmDb.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:RealmDb" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" DataContext="{Binding Source={x:Static local:App.MainViewModel}}"> <StackPanel> <Button Content="Populate" Command="{Binding PopulateCommand}" /> <Button Content="Remove" Command="{Binding RemoveCatCommand}" /> <DataGrid Name="dataGrid" AutoGenerateColumns="False" ItemsSource="{Binding Cats, Mode=TwoWay}" SelectedItem="{Binding SelectedCat, Mode=TwoWay}" > <DataGrid.Columns> <DataGridTextColumn Header="Name" Binding="{Binding Name}"/> <DataGridTextColumn Header="Age" Binding="{Binding Age}"/> <DataGridTextColumn Header="Breed" Binding="{Binding Breed}"/> </DataGrid.Columns> </DataGrid> </StackPanel> </Window>
Cat.cs
public partial class Cat : IRealmObject { [PrimaryKey] public ObjectId Id { get; set; } = ObjectId.GenerateNewId(); public string Name { get; set; } = string.Empty; public int Age { get; set; } = 0; public string Breed { get; set; } = string.Empty; }
App.xaml.cs
using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows; namespace RealmDb { /// <summary> /// App.xaml的交互逻辑 /// </summary> public partial class App : Application { private static MainWindowViewModel? _mainWindowsViewModel; public static MainWindowViewModel MainViewModel => _mainWindowsViewModel ??= new MainWindowViewModel(); } }
问题分析与解决办法
核心原因
WPF的ItemsControl对IQueryable<T>的集合变更通知支持不完善,而Realm默认返回的IQueryable<Cat>无法触发WPF需要的INotifyCollectionChanged事件。另外,删除对象后SelectedCat仍指向已被Realm标记为无效的对象,导致绑定访问时抛出异常。
具体解决方案
1. 使用Realm专用的可观察集合
Realm提供了RealmCollection<T>,它原生实现了INotifyCollectionChanged,WPF可以正确识别并自动刷新UI。修改ViewModel中的属性:
// 替换原有的IQueryable<Cat>类型 private RealmCollection<Cat> _cats; public RealmCollection<Cat> Cats { get => _cats; set => SetProperty(ref _cats, value); } // 构造函数中初始化 Cats = LocalRealm.All<Cat>().AsRealmCollection();
2. 删除后清空选中项
在删除操作完成后,立即清空SelectedCat,避免UI继续绑定已失效的对象:
[RelayCommand] private void RemoveCat() { if (SelectedCat == null) return; try { LocalRealm.Write(() => { LocalRealm.Remove(SelectedCat); }); // 清空选中项,消除无效对象访问异常 SelectedCat = null; } catch (Exception ex) { MessageBox.Show($"删除选中猫时出错。{ex.Message}"); } }
3. 优化批量写入
不需要分多次事务添加对象,同一个Write事务中可以完成多个对象的添加:
[RelayCommand] private void Populate() { Cat ninja = new() { Name = "Ninja", Age = 1, Breed = "Angora" }; Cat nounou = new() { Name = "Nounou", Age = 2, Breed = "Siamese" }; Cat leila = new() { Name = "Layla", Age = 3, Breed = "Local" }; try { LocalRealm.Write(() => { LocalRealm.Add(nounou); LocalRealm.Add(ninja); LocalRealm.Add(leila); }); } catch (RealmFileAccessErrorException ex) { MessageBox.Show($"写入Realm文件时出错。{ex.Message}"); } }
修改后的完整ViewModel代码
using System; using System.Windows; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using MongoDB.Bson; using Realms; using Realms.Exceptions; namespace RealmDb { public partial class MainWindowViewModel : ObservableObject { public MainWindowViewModel() { var config = new RealmConfiguration(@"d:\temp\cats.realm"); try { Realm.DeleteRealm(config); } catch(Exception ex){ } LocalRealm = Realm.GetInstance(config); Cats = LocalRealm.All<Cat>().AsRealmCollection(); } public Realm LocalRealm; private RealmCollection<Cat> _cats; public RealmCollection<Cat> Cats { get => _cats; set => SetProperty(ref _cats, value); } [ObservableProperty] private Cat? _selectedCat; [RelayCommand] private void RemoveCat() { if (SelectedCat == null) return; try { LocalRealm.Write(() => { LocalRealm.Remove(SelectedCat); }); SelectedCat = null; } catch (Exception ex) { MessageBox.Show($"删除选中猫时出错。{ex.Message}"); } } [RelayCommand] private void Populate() { Cat ninja = new() { Name = "Ninja", Age = 1, Breed = "Angora" }; Cat nounou = new() { Name = "Nounou", Age = 2, Breed = "Siamese" }; Cat leila = new() { Name = "Layla", Age = 3, Breed = "Local" }; try { LocalRealm.Write(() => { LocalRealm.Add(nounou); LocalRealm.Add(ninja); LocalRealm.Add(leila); }); } catch (RealmFileAccessErrorException ex) { MessageBox.Show($"写入Realm文件时出错。{ex.Message}"); } } } }
内容的提问来源于stack exchange,提问作者Mohamed Boulmers
相关产品推荐
相关产品推荐

