I am getting an ObjectDisposed Exception when adding and removing items from an Observable Collection.
Adding
If I add one new item to the collection it works fine, but if I try to add a second in the same running of the program I get an Object Disposed Exception. If I close and restart the program it will let me add 1 more but no more then that.
Removing
If I remove an Item that was added to the database from the last time I ran the program it works fine and deletes it. However if I add something in the same running and then try and delete what I just added it throws the exception.
Error message:
System.ObjectDisposedException: 'Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. Object name: 'RecipeDBContext'.'
I am using Entity Framework Core with an SqlLite Database, WinUi 3 and MVVM Community Toolkit.
View Model
public partial class UserViewModel : ObservableObject
{
RecipeDBContext context;
[ObservableProperty]
private ObservableCollection<User> _users;
public UserViewModel()
{
context = new RecipeDBContext();
_users = new ObservableCollection<User>();
UpdateUsers(context.User.ToList());
_users.CollectionChanged += this.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 != null)
{
string username = o as string;
if (username != String.Empty)
{
using (context)
{
var user = new User()
{
Username = username,
};
Users.Add(user);
}
}
}
}
Model
public partial class User: ObservableObject
{
public int Id { get; set; }
[ObservableProperty]
private string _username = string.Empty;
partial void OnUsernameChanged(string? oldValue, string newValue)
{
}
}
DbContext
public class RecipeDBContext: DbContext
{
public DbSet<User> User { get; set; }
public RecipeDBContext()
{
Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=recipe.db");
}
}
View
<Page
x:Class="Test.Views.UserPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Test.ViewModels"
mc:Ignorable="d"
xmlns:m="using:Test.Models"
x:Name="ThisPage">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.2*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Black" />
<TextBlock FontSize="30" Grid.Row="0" Grid.Column="0">LOGIN</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0">User</TextBlock>
<ListView x:Name="usersListView" Grid.Row="1" Grid.Column="1" ItemsSource="{x:Bind ViewModel.Users, Mode=TwoWay}">
<ListView.Header>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ListViewHeaderItem Grid.Column="0">
<TextBlock>Username</TextBlock>
</ListViewHeaderItem>
<ListViewHeaderItem Grid.Column="1">
<TextBlock>Recipes</TextBlock>
</ListViewHeaderItem>
<ListViewHeaderItem Grid.Column="2">
<TextBlock>Delete</TextBlock>
</ListViewHeaderItem>
</Grid>
</ListView.Header>
<ListView.ItemTemplate>
<DataTemplate x:DataType="m:User">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
<ColumnDefinition Width="0.3*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Username}" Grid.Column="0"></TextBlock>
<Button Grid.Column="2"
Command="{Binding ElementName=ThisPage, Path=ViewModel.DeleteUserCommand}"
CommandParameter="{x:Bind}"
Content="X"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<TextBlock Grid.Row="3" Grid.Column="0" FontSize="30">New User</TextBlock>
<TextBlock Grid.Row="4" Grid.Column="0">Name</TextBlock>
<TextBox x:Name="newUserName" Grid.Row="4" Grid.Column="1"></TextBox>
<Button x:Name="addUserBtn1" Command="{x:Bind ViewModel.AddUserCommand}" CommandParameter="{Binding Text, ElementName=newUserName}" Grid.Row="5" Grid.Column="2">Add User</Button>
</Grid>
</Page>
Looking at your code the AddUser
method has using(context)
which is calling context.Dispose()
at the end of the block - and you get a System.ObjectDisposedException
. Just omit using
for the time being.
[RelayCommand]
public void AddUser(object o) {
if (o != null) {
string username = o as string;
if (username != String.Empty) {
using (context) { // you are disposing
var user = new User() {
Username = username,
};
Users.Add(user);
}
}
}
}