Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wpf application freezes - massive memory leak puzzle

I hava a mvvm wpf application that worked properly all until one moment when it freezes and causes massive memory leak. Solution files: Google Disk Solution Link

The application uses local mdf file, uses Mahhaps , and some additional references (for example one for displaying gifs)

enter image description here

This is the method call in the View model that is making the issue, the assignment of the Contacts with await is making the issue - this is used in one other view model where it makes no any issues, even here it was working all right until one moment.

public async  void OnLoad()
        {
            IsRefreshEnabled = false;
            IsRefreshProgressActive = true;
            Contacts =await Task.Run(() => _repository.GetContactsAsync()) ;
            IsRefreshEnabled = true;
            IsRefreshProgressActive = false;
        }

This is the way the View is rendered

<DataGrid SelectedItem="{Binding Contact}" AutoGenerateColumns="True" ItemsSource="{Binding Path=Contacts, Mode=TwoWay}" Style="{StaticResource AzureDataGrid}"  x:Name="dataGridCodeBehind"  Margin="10,54,521,0" VerticalAlignment="Top"  HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" ColumnWidth="*">
            <DataGrid.ColumnHeaderStyle>
                <Style TargetType="DataGridColumnHeader">
                    <Setter Property="FontSize" Value="10"/>
                </Style>
            </DataGrid.ColumnHeaderStyle>
        </DataGrid>

I tried removing the libraries, for gifs(the gifs are not shown in this view in the other view which is not making any issues there).

The same call to the repository - to fetch the Contacts data I have on one other view and it is not making any problems.

This Contacts view was working good all until sudden.

I tried to debug but the debugger does not even come to the code.

After loading some data into the db I get a freeze.

The freeze is made by the OnLoad method from the ContactsViewModel - this call to the repository is used on one other ViewModel where it has no any issues - it returns the data quickly

ContactsViewModel code:

using Digital_Data_House_Bulk_Mailer.Commands;
using Digital_Data_House_Bulk_Mailer.Model;
using Digital_Data_House_Bulk_Mailer.Repository;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace Digital_Data_House_Bulk_Mailer.ViewModel
{
    class ContactsViewModel : INotifyPropertyChanged
    {
        private Contact _contact;

        private IContactRepository _repository = new ContactRepository();

        public event PropertyChangedEventHandler PropertyChanged = delegate { };

        public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;

        private static void NotifyStaticPropertyChanged(string propertyName)
        {
            if (StaticPropertyChanged != null)
                StaticPropertyChanged(null, new PropertyChangedEventArgs(propertyName));
        }

        public RelayCommand UpdateCommand { get; set; }

        public RelayCommand LoadCommand { get; set; }

        public RelayCommand DeleteCommand { get; set; }

        public RelayCommand DeleteAllContactsCommand { get; set; }

        public RelayCommand RecreateFiltersCommand { get; set; }

        private ObservableCollection<Contact> _contacts;

        public ObservableCollection<Contact> Contacts
        {
            get { return _contacts; }
            set
            {
                _contacts = value;
                PropertyChanged(this, new PropertyChangedEventArgs("Contacts"));
                //used in case of static Contacts property
                //NotifyStaticPropertyChanged("Contacts"); 
            }
        }


        public Contact Contact
        {
            get { return _contact; }
            set
            {
                _contact = value;
                DeleteCommand.RaiseCanExecuteChanged();
                UpdateCommand.RaiseCanExecuteChanged();
                PropertyChanged(this, new PropertyChangedEventArgs("Contact"));
            }
        }

        private bool _isRefreshEnabled=true;
        public bool IsRefreshEnabled
        {
            get { return _isRefreshEnabled; }
            set
            {
                _isRefreshEnabled = value;
                PropertyChanged(this, new PropertyChangedEventArgs("IsRefreshEnabled"));
            }
        }

        private bool _isRefreshProgressActive = false;
        public bool IsRefreshProgressActive
        {
            get { return _isRefreshProgressActive; }
            set
            {
                _isRefreshProgressActive = value;
                PropertyChanged(this, new PropertyChangedEventArgs("IsRefreshProgressActive"));
            }
        }

        public ContactsViewModel()
        {
            DeleteCommand = new RelayCommand(OnDelete, CanDelete);
            UpdateCommand = new RelayCommand(OnUpdate, CanUpdate);
            LoadCommand = new RelayCommand(OnLoad, CanLoad);
            DeleteAllContactsCommand = new RelayCommand(OnDeleteAllContacts, CanDeleteAllContacts);
            RecreateFiltersCommand = new RelayCommand(OnRecreateFilters, CanRecreateFilters);
            OnLoad();
        }

        public bool CanRecreateFilters()
        {
            return true;
        }

        public async void OnRecreateFilters()
        {
            IsRefreshProgressActive = true;
            await Task.Run(() => _repository.ResetFilters());
            IsRefreshProgressActive = false;
        }

        public async void OnDeleteAllContacts()
        {
            MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show("Are you sure?", "DELETE ALL EXISTING CONTACTS", System.Windows.MessageBoxButton.YesNo);
            if (messageBoxResult == MessageBoxResult.Yes)
            {
                IsRefreshProgressActive = true;
                await Task.Run(() => _repository.DeleteAllContacts());
                IsRefreshProgressActive = false;
            }
        }

        public bool CanDeleteAllContacts()
        {
            return true;
        }


        private void OnDelete()
        {
            MessageBoxResult messageBoxResult = System.Windows.MessageBox.Show("Are you sure?", "Delete Contact Confirmation", System.Windows.MessageBoxButton.YesNo);
            if (messageBoxResult == MessageBoxResult.Yes)
            {
                _repository.DeleteContactAsync(Contact);
                Contacts.Remove(Contact);
            }
        }

        private bool CanDelete()
        {          
            if (Contact != null )
            {
                return true;
            }
            return false;
        }

        private  void OnUpdate()
        {
             _repository.AddContactAsync(Contact);

        }

        private bool CanUpdate()
        {
            if (Contact != null )
            {
                return true;
            }
            return false;
        }


        public async  void OnLoad()
        {
            IsRefreshEnabled = false;
            IsRefreshProgressActive = true;
            Contacts =await Task.Run(() => _repository.GetContactsAsync()) ;
            IsRefreshEnabled = true;
            IsRefreshProgressActive = false;
        }

        private  ObservableCollection<Contact> GetContactsAsync()
        {
            return _repository.GetContactsAsync();
        }

        public bool CanLoad()
        {
            return true;
        }

    }
}

ContactRepository class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Digital_Data_House_Bulk_Mailer.Model;
using System.Data.Entity;
using System.Collections.ObjectModel;
using System.Data.Entity.Migrations;
using System.Collections;
using System.Data.Entity.Core.Objects;
using System.Data.SqlClient;

namespace Digital_Data_House_Bulk_Mailer.Repository
{
    class ContactRepository : IContactRepository
    {
        digital_datahouse_bulk_mailerEntities db = null;

        public ContactRepository()
        {
            db = new digital_datahouse_bulk_mailerEntities();
        }

        public string SELECT_ALL { get { return "Select All"; } private set { } }


        public  ObservableCollection<StateFilter> GetStates()
        {

            ObservableCollection<StateFilter> stateFilters = new ObservableCollection<StateFilter>();

            foreach (StateFilter state in db.StateFilters.ToList() )
            {
                db.Entry<StateFilter>(state).Reload();
                stateFilters.Add(state);
            }



            return stateFilters;
        }

        public ObservableCollection<CountyFilter> GetCounties()
        {
            ObservableCollection<CountyFilter> countyFilters = new ObservableCollection<CountyFilter>();

            foreach (CountyFilter county in db.CountyFilters.ToList())
            {
                db.Entry<CountyFilter>(county).Reload();
                countyFilters.Add(county);
            }

            return countyFilters;
        }

        public ObservableCollection<GenderFilter> GetGenders()
        {
            ObservableCollection<GenderFilter> genderFilters = new ObservableCollection<GenderFilter>();

            foreach (GenderFilter gender in db.GenderFilters.ToList())
            {
                db.Entry<GenderFilter>(gender).Reload();
                genderFilters.Add(gender);
            }

            return genderFilters;
        }

        public ObservableCollection<IndustryFilter> GetIndustries()
        {
            ObservableCollection<IndustryFilter> industryFilters = new ObservableCollection<IndustryFilter>();

            foreach (IndustryFilter industry in db.IndustryFilters.ToList())
            {
                db.Entry<IndustryFilter>(industry).Reload();
                industryFilters.Add(industry);
            }

            return industryFilters;
        }

        public ObservableCollection<IsContactedFilter> GetIsContacted()
        {
            ObservableCollection<IsContactedFilter> isContactedFilters = new ObservableCollection<IsContactedFilter>();

            foreach (IsContactedFilter isContacted in db.IsContactedFilters.ToList())
            {
                db.Entry<IsContactedFilter>(isContacted).Reload();
                isContactedFilters.Add(isContacted);
            }

            return isContactedFilters;
        }

        public ObservableCollection<SicCodeDescriptionFilter> GetSicCodeDescriptions()
        {
            ObservableCollection<SicCodeDescriptionFilter> sicCodeDescriptionFilters = new ObservableCollection<SicCodeDescriptionFilter>();

            foreach (SicCodeDescriptionFilter sicCodeDescriptionFilter in db.SicCodeDescriptionFilters.ToList())
            {
                db.Entry<SicCodeDescriptionFilter>(sicCodeDescriptionFilter).Reload();
                sicCodeDescriptionFilters.Add(sicCodeDescriptionFilter);
            }

            return sicCodeDescriptionFilters;
        }



        public void AddContactAsync(Contact contact)
        {
            if (contact != null)
            {
                db.Contacts.AddOrUpdate(contact);
                db.SaveChangesAsync();
            }
        }

        public void DeleteContactAsync(Contact contact)
        {
            if (contact != null)
            {
                db.Contacts.Remove(contact);
                db.SaveChangesAsync();
            }
        }

        public void UpdateContactAsync(Contact contact)
        {
            if (contact != null)
            {
                db.Contacts.AddOrUpdate(contact);
                db.SaveChangesAsync();
            }
        }

        public ObservableCollection<Contact> GetContactsAsync()
        {

            db = new digital_datahouse_bulk_mailerEntities();

            ObservableCollection<Contact> contacts = new ObservableCollection<Contact>();

            foreach (var contact in db.Contacts.ToList())
            {
                contacts.Add(contact);
            }
            return contacts;
        }

        public ObservableCollection<Contact> FilterContacts(ObservableCollection<StateFilter> states, ObservableCollection<CountyFilter> counties, 
            ObservableCollection<GenderFilter> genders, ObservableCollection<IndustryFilter> industries, ObservableCollection<IsContactedFilter> contacted, 
            ObservableCollection<SicCodeDescriptionFilter> codes, bool hasWebsite)
        {

            db = new digital_datahouse_bulk_mailerEntities();

            ObservableCollection<Contact> filteredContacts = new ObservableCollection<Contact>();

            string[] stateArray = (from s in states where s.IsChecked==true select s.State).ToArray();
            string[] countyArray= (from c in counties where c.IsChecked==true select c.County).ToArray();
            string[] genderArray= (from g in genders where g.IsChecked==true select g.Gender).ToArray();
            string[] industryArray = (from i in industries where i.IsChecked==true select i.Industry).ToArray();
            string[] contactedArray = (from c in contacted where c.IsChecked==true select c.IsContacted.ToString()).ToArray();
            string[] sicCodeArray = (from c in codes where c.IsChecked==true select c.SicCodeDescription).ToArray();

            var contacts=(from c in db.Contacts
                          where
                          stateArray.Contains(c.State) &&
                          countyArray.Contains(c.County) &&
                          genderArray.Contains(c.Gender) &&
                          industryArray.Contains(c.Industry) &&
                          contactedArray.Contains(c.IsContacted) &&
                          sicCodeArray.Contains(c.SIC_Code_Description)

                          select c).ToList();

            foreach (Contact contact in contacts)
            {
                if(hasWebsite==true)
                {
                    if(!String.IsNullOrEmpty(contact.WebSite))
                    {
                        filteredContacts.Add(contact);
                    }
                }
                else
                {
                    filteredContacts.Add(contact);
                }

            }

            return filteredContacts;
        }

        public void AddContactsRange(ObservableCollection<Contact> contacts)
        {

                db.Contacts.AddRange(contacts);
                db.SaveChanges();

        }

        public void ResetFilters()
        {
            ResetStates();
            ResetCounties();
            ResetIsContactedFilters();
            ResetGenders();
            ResetIndustries();
            ResetSicFilters(); 

        }

        private void ResetStates()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [StateFilter]");

            db = new digital_datahouse_bulk_mailerEntities();

            List<StateFilter> stateFilters = new List<StateFilter>();

            var states = (
                from c in db.Contacts
                select c.State

                ).Distinct().ToList();

            foreach (var stateName in states)
            {
                StateFilter state = new StateFilter();
                state.State = stateName;
                state.IsChecked = true;
                stateFilters.Add(state);

            }


            db.StateFilters.AddRange(stateFilters);
            db.SaveChanges();

        }

        public void DeleteAllContacts()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [Contact]");           
            db = new digital_datahouse_bulk_mailerEntities();   
        }

        private void ResetCounties()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [CountyFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<CountyFilter> countyFilters = new List<CountyFilter>();

            var counties = (
                from c in db.Contacts
                select c.County

                ).Distinct().ToList();

            foreach (var countyName in counties)
            {
                CountyFilter county = new CountyFilter();
                county.County = countyName;
                county.IsChecked = true;
                countyFilters.Add(county);
            }

            db.CountyFilters.AddRange(countyFilters);
            db.SaveChanges();
        }

        private void ResetGenders()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [GenderFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<GenderFilter> genderFilters = new List<GenderFilter>();

            var genders = (
                from c in db.Contacts
                select c.Gender

                ).Distinct().ToList();

            foreach (var genderName in genders)
            {
                GenderFilter gender = new GenderFilter();
                gender.Gender = genderName;
                gender.IsChecked = true;
                genderFilters.Add(gender);
            }

            db.GenderFilters.AddRange(genderFilters);
            db.SaveChanges();
        }

        private void ResetIndustries()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [IndustryFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<IndustryFilter> industryFilters = new List<IndustryFilter>();

            var industries = (
                from c in db.Contacts
                select c.Industry

                ).Distinct().ToList();

            foreach (var industryName in industries)
            {
                IndustryFilter industry = new IndustryFilter();
                industry.Industry = industryName;
                industry.IsChecked = true;
                industryFilters.Add(industry);
            }

            db.IndustryFilters.AddRange(industryFilters);
            db.SaveChanges();
        }

        private void ResetIsContactedFilters()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [IsContactedFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<IsContactedFilter> isContactedFilters = new List<IsContactedFilter>();

            var isContacted = (
                from c in db.Contacts
                select c.IsContacted

                ).Distinct().ToList();

            foreach (var contactedName in isContacted)
            {
                IsContactedFilter contacted = new IsContactedFilter();
                contacted.IsContacted = contactedName;
                contacted.IsChecked = true;
                isContactedFilters.Add(contacted);
            }

            db.IsContactedFilters.AddRange(isContactedFilters);
            db.SaveChanges();
        }

        private void ResetSicFilters()
        {
            db.Database.ExecuteSqlCommand("TRUNCATE TABLE [SicCodeDescriptionFilter]");
            db = new digital_datahouse_bulk_mailerEntities();

            List<SicCodeDescriptionFilter> sicFilters = new List<SicCodeDescriptionFilter>();

            var sics = (
                from c in db.Contacts
                select c.SIC_Code_Description

                ).Distinct().ToList();

            foreach (var sic in sics)
            {
                SicCodeDescriptionFilter sicCode = new SicCodeDescriptionFilter();
                sicCode.SicCodeDescription = sic;
                sicCode.IsChecked = true;
                sicFilters.Add(sicCode);
            }

            db.SicCodeDescriptionFilters.AddRange(sicFilters);
            db.SaveChanges();
        }

        public void UpdateIsContactedInformation(Contact contact)
        {
            db = new digital_datahouse_bulk_mailerEntities();

            contact.IsContacted = "True";
            contact.MessageDateSent = DateTime.Today;

            db.Contacts.AddOrUpdate(contact);
            db.SaveChanges();
        }
    }
}

This is the view that uses the ViewModel it is called ContactsView - this view is included in the MainWindow as other views are

ContactsView :

<UserControl x:Class="Digital_Data_House_Bulk_Mailer.View.ContactsView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:Digital_Data_House_Bulk_Mailer.View"
         xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
         xmlns:model="clr-namespace:Digital_Data_House_Bulk_Mailer.ViewModel"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
    <model:ContactsViewModel/>
</UserControl.DataContext>
<Grid Margin="0,0,-690,-36" >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="61*"/>
        <ColumnDefinition Width="10*"/>
    </Grid.ColumnDefinitions>
    <Button  Style="{StaticResource AccentedSquareButtonStyle}" Command="{Binding DeleteCommand}" Controls:TextBoxHelper.ClearTextButton="True" x:Name="delete" Content="DELETE" HorizontalAlignment="Left" Margin="115,21,0,0" VerticalAlignment="Top"  Width="100" RenderTransformOrigin="0.267,0.519"/>
    <Button  Style="{StaticResource AccentedSquareButtonStyle}" Command="{Binding UpdateCommand}" Controls:TextBoxHelper.ClearTextButton="True" x:Name="update" Content="add / update" HorizontalAlignment="Left" Margin="10,21,0,0" VerticalAlignment="Top"  Width="100" RenderTransformOrigin="0.267,0.519"/>
    <Button  Style="{StaticResource AccentedSquareButtonStyle}"  Controls:TextBoxHelper.ClearTextButton="True" x:Name="bulk_import" Content="import new data" HorizontalAlignment="Left" Margin="220,21,0,0" VerticalAlignment="Top"  Width="110" RenderTransformOrigin="0.267,0.519" Click="bulk_import_Click"/>
    <DataGrid SelectedItem="{Binding Contact}" AutoGenerateColumns="True" ItemsSource="{Binding Path=Contacts, Mode=TwoWay}" Style="{StaticResource AzureDataGrid}"  x:Name="dataGridCodeBehind"  Margin="10,54,521,0" VerticalAlignment="Top"  HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" ColumnWidth="*">
        <DataGrid.ColumnHeaderStyle>
            <Style TargetType="DataGridColumnHeader">
                <Setter Property="FontSize" Value="10"/>
            </Style>
        </DataGrid.ColumnHeaderStyle>
    </DataGrid>
    <Button IsEnabled="{Binding IsRefreshEnabled}" Style="{StaticResource AccentedSquareButtonStyle}" Command="{Binding LoadCommand}" x:Name="refreshData" Content="Refresh Contacts" HorizontalAlignment="Left" Height="22" Margin="335,21,0,0" VerticalAlignment="Top" Width="114"/>
    <Controls:ProgressRing IsActive="{Binding IsRefreshProgressActive}" Foreground="{DynamicResource AccentColorBrush}" Margin="720,0,0,0" RenderTransformOrigin="-2.889,0.463" HorizontalAlignment="Left" VerticalAlignment="Top" Height="50" Width="50"/>
    <Button x:Name="deleteContacts" Command="{Binding DeleteAllContactsCommand}" Style="{StaticResource AccentedSquareButtonStyle}" Content="Delete All Contacts" HorizontalAlignment="Left" Height="22" Margin="454,21,0,0" VerticalAlignment="Top" Width="114"/>
    <Button x:Name="recreateFilters" Command="{Binding RecreateFiltersCommand}" Content="Recreate TO MAIL Filters" HorizontalAlignment="Left" Style="{StaticResource AccentedSquareButtonStyle}" Height="22" Margin="573,21,0,0" VerticalAlignment="Top" Width="142"/>

</Grid>

The very bad thing is that when I try to debug it does not even go to the code - method call. Tried also to use some profiling tools but I did not come to any conclusion what is making this freeze issue...

When I run the query on the db from the db editor I get instant results .

I also use a SSRS report in the app displayed using ReportViewer - it also works fine and returns the same data - it is displayed on a separate View.

On one of the views I display a GIF animation using WpfAnimatedGif Library - I tried to remove this reference to see if that is making the issue but it does not the freeze goes on...

I also tried to rewrite my repository class to use using command for the creation of the new instance of db for every method but this is not what is making the issue.

Solution files: Google Disk Solution Link

like image 836
Jovo Krneta Avatar asked May 19 '18 08:05

Jovo Krneta


1 Answers

I finally solved this issue. What I needed is to add the Height="500" property to the data grid.

Removing the Height property makes the DataGrid freeze when displaying more than 50 rows in my case. With the added Height it loads 5000 records in less than a second.

like image 181
Jovo Krneta Avatar answered Nov 15 '22 13:11

Jovo Krneta