Search code examples
c#data-bindingentity-framework-corewinui-3winui

Object Disposed Exception When Adding/Removing From Observable Collection


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>

Solution

  • 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);
                }
            }
        }
    }