UI Slows Down When Listing Large Data (3000+ Records) in WPF Application

fatih uyanık 225 Reputation points
2025-03-21T07:37:55.05+00:00

Hello, 

 

I am developing a library automation system using WPF and MVVM. My application contains more than 3000 book records, which I list using ICollectionView and ObservableCollection. However, while loading and displaying the data, the UI slows down significantly. 

 

__Technologies I Am Using: __

  • WPF (.NET 8) + MVVM (CommunityToolkit.MVVM) 
  • Entity Framework Core + SQLite 
  • ICollectionView and ObservableCollection for data listing 

 

__Issues I Am Facing: __

  1. The application runs slowly and the UI freezes, especially while loading data and adding items to the list. 
  2. The initial loading time is quite long (2-3 seconds). 

 

How can I solve this performance issue while keeping my current structure or using an alternative approach? 

What are your suggestions to overcome this problem? 

 

I would greatly appreciate your experiences and recommendations. 

Thank you!

using CommunityToolkit.Mvvm.Input;
using Kütüphane_Otomasyonu.BusinessLayer.Services.Interfaces;
using Kütüphane_Otomasyonu.Common;
using Kütüphane_Otomasyonu.DataLayer.Enums;
using Kütüphane_Otomasyonu.DataLayer.Migrations;
using Kütüphane_Otomasyonu.DataLayer.Models;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Data;
namespace Kütüphane_Otomasyonu.BusinessLayer.ViewModels.Base
{
    public abstract class BaseListViewModel<T> : BaseViewModel, INavigatable where T : class, new()
    {
        //Komutlar
        public RelayCommand AddCommand { get; private set; }
        public RelayCommand<object> ItemClickedCommand { get; private set; }
        public RelayCommand CloseCommand { get; private set; }
        // Servisler
        private readonly IBaseService<T> baseService;
        private readonly INavigationService navigationService;
        protected CustomObservableCollection<T> DataList;
        private ICollectionView _dataCollectionView;
        public ICollectionView DataCollectionView
        {
            get => _dataCollectionView;
            private set => SetProperty(ref _dataCollectionView, value);
        }
        protected BaseListViewModel(IBaseService<T> baseService, INavigationService navigationService)
        {
            this.baseService = baseService;
            this.navigationService = navigationService;
            Initialize();
        }
        private async Task Initialize()
        {
            //Komutlar
            AddCommand = new RelayCommand(AddCommandExecute, AddCommandCanExecute);
            ItemClickedCommand = new RelayCommand<object>(OnItemClickedCommandExecute);
            CloseCommand = new RelayCommand(CloseCommandExecute, CloseCommandCanExecute);
            await LoadDataAsync();
                   }
        private async Task LoadDataAsync()
        {
            var datas = await baseService.GetAllAsync();
            DataList = new CustomObservableCollection<T>(datas);
            // DataList veri kaynağını kullanarak bir ICollectionView nesnesi oluşturulur.
            DataCollectionView = CollectionViewSource.GetDefaultView(DataList);
                               }
        protected abstract void AddCommandExecute();
        protected abstract void OnItemClickedCommandExecute(object obj);
        protected virtual private void CloseCommandExecute()
        {
            navigationService.CloseWindow();
        }
        private bool AddCommandCanExecute() => true;
        private bool CloseCommandCanExecute() => true;
        public async void OnNavigatedToAsync(object parameter)
        {
            if (parameter is bool)
            {
                await LoadDataAsync();
            }
        }
    }
}
using CommunityToolkit.Mvvm.Input;
using Kütüphane_Otomasyonu.BusinessLayer.Services.Interfaces;
using Kütüphane_Otomasyonu.BusinessLayer.ViewModels.Base;
using Kütüphane_Otomasyonu.Common;
using Kütüphane_Otomasyonu.DataLayer.Models;
using Kütüphane_Otomasyonu.PresentationLayer.Views.BookViews;
namespace Kütüphane_Otomasyonu.BusinessLayer.ViewModels.BookViewModels
{
    public class BooksViewModel : BaseListViewModel<Book>
    {
        private readonly INavigationService navigationService;
        private readonly IBookService bookService;
        private Book _selectedItem;
        public Book SelectedItem
        {
            get => _selectedItem;
            set
            {
                SetProperty(ref _selectedItem, value);
                HelperMethods.CommandsNotifyChange(new RelayCommand[] { AddCommand, CloseCommand });
            }
        }
        public BooksViewModel(INavigationService navigationService, IBookService bookService) : base(bookService, navigationService)
        {
            this.navigationService = navigationService;
            this.bookService = bookService;
            Initialize();
        }
        private async void Initialize()
        {
            //             Verilerin doldurulması.
            var books = await bookService.GetAllAsync();
            DataList = new CustomObservableCollection<Book>(books);
        }
        // TODO SelectedItem üzerinden geçerli öğe alınacak şekilde ayarlanacak.
        protected override void OnItemClickedCommandExecute(object obj)
        {
            if (obj != null)
            {
                _selectedItem = obj as Book;
                navigationService.NavigateToView<BookDetailView>(SelectedItem);
            }
        }
        protected override void AddCommandExecute()
        {
            navigationService.ShowWindow<NewBookView>();
        }
        private bool AddCanChanged() => SelectedItem != null || SelectedItem == null;
    }
}
using FluentValidation;
using Kütüphane_Otomasyonu.BusinessLayer.Services.Helpers;
using Kütüphane_Otomasyonu.BusinessLayer.Services.Interfaces;
using Kütüphane_Otomasyonu.BusinessLayer.Validators.ErrorMessages;
using Kütüphane_Otomasyonu.Common;
using Kütüphane_Otomasyonu.DataLayer.Interfaces;
using Kütüphane_Otomasyonu.DataLayer.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Kütüphane_Otomasyonu.BusinessLayer.Services.Implementations
{
    public class BookService : BaseService<Book>, IBookService
    {
        private readonly IAuthorRepository authorRepository;
        private readonly IPublisherRepository publisherRepository;
        private readonly IBookRepository bookRepository;
        private readonly IPublisherService publisherService;
        private readonly IValidator<Book> validator;
        private readonly IValidationService<Book> validationService;
        private readonly IAuthorService authorService;
        /// <summary>
        /// Kitap ile ilgili işleri yürütürken kullanılacak servis sınıfı
        /// </summary>
        /// <param name="dataAccess"> Veri erişim sınıfı</param>
        public BookService(IAuthorRepository authorRepository, IPublisherRepository publisherRepository, IBookRepository bookRepository, IPublisherService publisherService, IValidator<Book> validator, IValidationService<Book> validationService, IAuthorService authorService) : base(bookRepository, validationService, validator)
        {
            this.authorRepository = authorRepository;
            this.publisherRepository = publisherRepository;
            this.bookRepository = bookRepository;
            this.publisherService = publisherService;
            this.validator = validator;
            this.validationService = validationService;
            this.authorService = authorService;
            Initialize();
        }
        private void Initialize()
        {
        }
        
                public override async Task<IEnumerable<Book>> GetAllAsync()
        {
                       return await bookRepository.GetAllAsync(
                A => A.Author,
                P => P.publisher
                );
        }
        protected override async Task<Result<Book>> ValidateAsync(Book entity)
        {
            // Temel validasyonları gerçekleştir
            var baseValidate = await base.ValidateAsync(entity);
            if (!baseValidate.IsSuccess)
                return Result<Book>.Fail(baseValidate.ErrorMessage);
            // barkod Geçerlilik kontrolü
            if (!IsValidBarcode(entity.Barcode))
                return Result<Book>.Fail(BookMessages.InvalidBarcode);
            // Barkod numarası kullanılmış mı?
            var barcodeUsed = await bookRepository.AnyAsync(a => a.Barcode == entity.Barcode && a.Id != entity.Id);
            if (barcodeUsed)
                return Result<Book>.Fail(BookMessages.BarcodeUsed);
            // Kitap numarası kullanılmış mı?
            var bookNumberUsed = await IsBarcodeUsedAsync(entity.Barcode, entity.Id);
            if (bookNumberUsed)
                return Result<Book>.Fail(BookMessages.BookNumberUsed);
            // Yazar validasyonu 
            var authorValidate = await authorService.ValidateModelAsync(entity.Author);
            if (!authorValidate.IsSuccess)
                return Result<Book>.Fail(authorValidate.ErrorMessage);
            // Yayınevi validasyonu
            var publisherValidate = await publisherService.ValidateModelAsync(entity.publisher);
            if (!publisherValidate.IsSuccess)
                return Result<Book>.Fail(publisherValidate.ErrorMessage);
            return Result<Book>.Success(entity);
        }
        /// <summary>
        /// Bir sonraki demirbaş numarasını döndürür.
        /// </summary>
        /// <returns> Task<int> </returns>
        public async Task<int> GetNextBookNumberAsync()
        {
            return await bookRepository.MaxAsync(d => d.BookNumber) + 1;
        }
        /// <summary>
        /// Yazar var mı kontrol eder
        /// </summary>
        /// <param name="publisherName"> Yazar adı</param>
        /// <returns>< task<Publisher>/returns>
        public async Task<Publisher> ExistPublisher(string publisherName)
        {
            return await publisherRepository.ExistPublisher(publisherName);
        }
        /// <summary>        
        /// Bu numaralı bir kitap var mı kontrol eder
        /// </summary>
        /// <param name="number">Kitap numarası</param>
        /// <returns> Task<bool> </returns>
        public async Task<bool> IsBookNumberInUseAsync(int number)
        {
            return await bookRepository.AnyAsync(a => a.BookNumber == number);
        }
        // Barkod geçerli mi? 
        private bool IsValidBarcode(string barcode)
        {
            if (string.IsNullOrEmpty(barcode))
                return false;
            var barcodeGenerator = new BarcodeGenerator();
            return barcodeGenerator.BarcodeIsValid(barcode);
        }
        // Barkod kullanılmış mı?
        private async Task<bool> IsBarcodeUsedAsync(string barcode, int? bookId = null)
        {
            return await bookRepository.AnyAsync(a => a.Barcode == barcode && a.Id != bookId);
        }
        // Kitap numarası kullanılmış mı?
        private async Task<bool> IsBookNumberUsedAsync(int bookNumber, int? bookId = null)
        {
            // Kitap numarası kullanılmış mı?
            return await bookRepository.AnyAsync(a => a.BookNumber == bookNumber && a.Id != bookId);
        }
    }
}
Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,853 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Hongrui Yu-MSFT 5,255 Reputation points Microsoft External Staff
    2025-03-21T09:51:03.2033333+00:00

    Hi, @fatih uyanık. Welcome to Microsoft Q&A. 

    You could refer to the following optimization solutions:

    1.Paging Use paging technology: Divide book records into small batches for loading. This could reduce UI freezing and speed up initial loading time. You could implement paging in EF Core through Skip() and Take() methods.

    2.Virtualization Use virtualized controls: In WPF, you could virtualize the display of the list through VirtualizingStackPanel. You could refer to the document for more detailed operations.

    3.Simplify UI Do not use particularly complex Style and binding Converter, especially when you need to load a large amount of data at one time.

    4.Asynchronous operation Make sure the code for loading data is asynchronous. For example, use async and await to load data and update ObservableCollection at the same time. Avoid blocking the UI thread.

    5.Background thread loading Load data in the background thread and then update ObservableCollection in the UI thread. You can use Task.Run() to achieve this. It is not recommended that you call Initialize directly in the constructor. You could consider calling Initialize asynchronously in the Loaded event. If you must call Initialize in the constructor, consider using Task.Run() to call Initialize.

    6.Optimize database queries

    a. Selective fields: Load only necessary fields in the query, not all fields. For example: use .Select(b => new { b.Title, b.Author }).

    b. Index: In SQLite database, create indexes for key fields (such as book title or author fields) to speed up queries.

    c. Use lazy loading Enable lazy loading in EF Core to load only the fields that need to be displayed to reduce memory consumption.


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.