操作Observable Collection时触发ObjectDisposedException问题求助
问题根源
你遇到的ObjectDisposedException是因为在AddUser方法中使用using (context)导致全局的RecipeDBContext实例被提前释放:
- 首次添加时,
using代码块内执行Users.Add(user)触发CollectionChanged,此时context尚未被释放,SaveChanges正常执行; using代码块结束后,context.Dispose()被自动调用,context实例失效;- 后续第二次添加、删除本次运行添加的项时,
CollectionChanged事件中尝试使用已释放的context,就会抛出异常; - 删除上一次运行添加的项看似正常,是因为这些实体是程序启动时从数据库加载的,可能已经脱离了当前
context的跟踪,但本质上此时context已经失效,后续操作仍会出问题。
解决方案
1. 移除AddUser中的using (context)
ViewModel中持有全局的context实例,不需要在单次操作中手动释放,直接移除using块:
[RelayCommand] public void AddUser(object o) { if (o is not string username || string.IsNullOrEmpty(username)) return; var user = new User() { Username = username, }; Users.Add(user); }
2. 让ViewModel实现IDisposable,正确释放资源
ViewModel销毁时,需要取消事件订阅并释放context:
public partial class UserViewModel : ObservableObject, IDisposable { private RecipeDBContext _context; [ObservableProperty] private ObservableCollection<User> _users; public UserViewModel() { _context = new RecipeDBContext(); _users = new ObservableCollection<User>(); UpdateUsers(_context.User.ToList()); _users.CollectionChanged += OnCollectionChanged; } public void UpdateUsers(List<User> users) { _users.Clear(); foreach (User user in users) { Users.Add(user); } } void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (User newItem in e.NewItems) { _context.User.Add(newItem); } } if (e.OldItems != null) { foreach (User oldItem in e.OldItems) { _context.User.Remove(oldItem); } } _context.SaveChanges(); } [RelayCommand] private void DeleteUser(User user) { Users.Remove(user); } [RelayCommand] public void AddUser(object o) { if (o is not string username || string.IsNullOrEmpty(username)) return; var user = new User() { Username = username, }; Users.Add(user); } public void Dispose() { _users.CollectionChanged -= OnCollectionChanged; _context.Dispose(); } }
3. 推荐使用依赖注入管理DbContext生命周期(最佳实践)
手动创建DbContext容易出现生命周期管理问题,WinUI 3中可以通过依赖注入来托管DbContext:
步骤1:在App.xaml.cs中注册服务
using Microsoft.Extensions.DependencyInjection; using Microsoft.UI.Xaml; namespace Test; public partial class App : Application { public IServiceProvider Services { get; } public App() { InitializeComponent(); // 构建服务容器 var services = new ServiceCollection(); // 注册DbContext,使用SQLite services.AddDbContext<RecipeDBContext>(options => options.UseSqlite("Data Source=recipe.db")); // 注册ViewModel services.AddTransient<UserViewModel>(); Services = services.BuildServiceProvider(); } protected override void OnLaunched(LaunchActivatedEventArgs args) { var window = new Window(); var userPage = new UserPage(); // 从服务容器获取ViewModel并赋值 userPage.ViewModel = Services.GetRequiredService<UserViewModel>(); window.Content = userPage; window.Activate(); } }
步骤2:修改ViewModel构造函数,通过注入获取DbContext
public partial class UserViewModel : ObservableObject, IDisposable { private readonly RecipeDBContext _context; [ObservableProperty] private ObservableCollection<User> _users; // 通过构造函数注入获取DbContext public UserViewModel(RecipeDBContext context) { _context = context; _users = new ObservableCollection<User>(); UpdateUsers(_context.User.ToList()); _users.CollectionChanged += OnCollectionChanged; } // ... 其他方法保持不变 ... public void Dispose() { _users.CollectionChanged -= OnCollectionChanged; // 依赖注入会管理DbContext的释放,这里可以不用手动调用Dispose, // 但如果是Transient生命周期,手动释放也没问题 _context.Dispose(); } }
步骤3:确保View中正确使用ViewModel
在UserPage.xaml.cs中添加ViewModel属性:
public sealed partial class UserPage : Page { public UserViewModel ViewModel { get; set; } public UserPage() { InitializeComponent(); } }
这样依赖注入容器会自动处理DbContext的创建和释放,彻底避免手动管理生命周期带来的问题。
内容的提问来源于stack exchange,提问作者Nuvolari
相关产品推荐
相关产品推荐

