Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Tutorial for a Basic WPF – MVVM Project Using Entity Framework

0.00/5 (No votes)
17 Feb 2015 1  
This is an alternative for Tutorial for a Basic WPF – MVVM Project Using Entity Framework

Introduction

The tutorial created by Richard Protzel is a great example of using Entity Framework in WPF, however like most examples of its kind - its not designed to scale to large data sets.

The idea of this article is to show how you can take a simple example, and by dropping in the VirtualObservableCollection, end up with a system that can scale to pretty well any data set size you want.

Background

This is an extension of the tutorial seen at: http://www.codeproject.com/Articles/873592/Tutorial-for-a-Basic-WPF-MVVM-Project-Using-Entity

Using the VirtualizingObservableCollection, seen at: http://www.codeproject.com/Articles/874363/Virtualizing-Data-with-XAML

Using the code

Dropping in the VirtualizingObservableCollection, is as simple as defining a provider for the data, instead of 'Filling' the Lists, and instead returning a VirtualizingObservableCollection using the provider instead of returning the filled list.

In this example we have two lists - One which is a list of Authors, and one which is a list of Books for that Author.

In our case, we are going to create an author provider, using the Paging System. That way, we only load little chunks at a time, rather than waiting for the whole list to propulate each time.

If you have downloaded the project from the first article (Richards), we are going to define a provider that returns an authors collection - in MainWindowViewModel.cs we need to add:

        public class AuthorProvider : IPagedSourceProvider<Author>
        {
            private AuthorBookEntities _ctx = null;

            public AuthorProvider(AuthorBookEntities ctx)
            {
                _ctx = ctx;
            }

            public int Count
            {
                get { return (from a in _ctx.Authors select a).Count(); }
            }

            public PagedSourceItemsPacket<Author> GetItemsAt(int pageoffset, int count, bool usePlaceholder)
            {
                return new PagedSourceItemsPacket<Author>() { LoadedAt = DateTime.Now, Items = (from a in _ctx.Authors orderby a.AuthorName select a).Skip(pageoffset).Take(count) };
            }

            public int IndexOf(Author item)
            {
                return (from a in _ctx.Authors orderby a.AuthorName where a.AuthorName.CompareTo(item.AuthorName)<0 select a).Count();
            }

            public void OnReset(int count)
            {
            }
        }

In our example, we pass the entity context into the constructor, as we are going to need it, then we implement the IPagedSourceProvider contract, which is three methods and a property:-

Count - This is used to tell the ItemsSource to define how many items the collection has. Note we dont actually get the items, instead we just return the count.

GetItemsAt - In this case, we simply return the items using a Skip and Take - basically pull back a page of data - we are using the default, so it pulls back about 100 items at a time.

IndexOf - This is used when the item is not wired (i.e. its not in a page that we have 'seen'), this is used by the Combo selection system - but very infrequently...

OnReset - We dont need to do anything clever here. An empty method is just fine.

Next we replace the orginal Authors List with a VirtualizingObservableCollection and instead of filling that list, we return the collection instead:

So:

        private void FillAuthors()
        {
            var q = (from a in ctx.Authors
                     select a).ToList();
            this.Authors = q;
        }
 
        private List<Author> _authors;
        public List<Author> Authors
        {
            get
            {
                return _authors;
            }
            set
            {
                _authors = value;
                NotifyPropertyChanged();
            }
        }

Is replaced by:

        private void FillAuthors()
        {
            Authors = new VirtualizingObservableCollection<Author>(new PaginationManager<Author>(new AuthorProvider(this.ctx)));
        }

        private VirtualizingObservableCollection<Author> _authors;
        public VirtualizingObservableCollection<Author> Authors
        {
            get
            {
                return _authors;
            }
            set
            {
                _authors = value;
                NotifyPropertyChanged();
            }
        }

Next, we need to write a provider to access the books for the author - in this case we need to pass in two things - The Entity context, and the Author that we want to use to filter the books on.

In our case, we pass these in via the contructor, so the provider looks like this:

        public class BookProvider : IPagedSourceProvider<Book>
        {
            private AuthorBookEntities _ctx = null;

            private Author _Author = null;
           
            public BookProvider(AuthorBookEntities ctx, Author author)
            {
                _ctx = ctx;
                _Author = author;
            }

            public int Count
            {
                get { return (from b in _ctx.Books where b.AuthorId == _Author.AuthorId orderby b.Title select b).Count(); }
            }

            public PagedSourceItemsPacket<Book> GetItemsAt(int pageoffset, int count, bool usePlaceholder)
            {
                return new PagedSourceItemsPacket<Book>() { LoadedAt = DateTime.Now, Items = (from b in _ctx.Books where b.AuthorId == _Author.AuthorId orderby b.Title select b).Skip(pageoffset).Take(count) };
            }

            public int IndexOf(Book item)
            {
                return (from b in _ctx.Books where b.AuthorId == _Author.AuthorId && b.Title.CompareTo(item.Title)<0 orderby b.Title select b).Count() ;
            }

            public void OnReset(int count)
            {
            }
        }

As you can see, its very similar to the Authors provider (apart from it returns books !), the only difference is the extra argument to the constructor for the Author, and the use of that Author object in the Where clause to filter it to just that author.

Next we need to replace the 'fillbooks' to use the provider, and replace the List<Books> with the virtualizing collection, so the orginal code was:

        private void FillBook()
        {
            Author author = this.SelectedAuthor;
 
            var q = (from book in ctx.Books
                     orderby book.Title
                     where book.AuthorId == author.AuthorId
                     select book).ToList();
 
            this.Books = q;
        }
 
        private List<Book> _books;
        public List<Book> Books
        {
            get
            {
                return _books;
            }
            set
            {
                _books = value;
                NotifyPropertyChanged();
            }
        }

Becomes:

        private void FillBook()
        {
            Author author = this.SelectedAuthor;

            this.Books = new VirtualizingObservableCollection<Book>(new PaginationManager<Book>(new BookProvider(this.ctx, author)));
        }

        private VirtualizingObservableCollection<Book> _books = null;
        public VirtualizingObservableCollection<Book> Books
        {
            get
            {
                return _books;
            }
            set
            {
                _books = value;
                NotifyPropertyChanged();
            }
        }

So, our new ViewModel looks like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using AlphaChiTech.Virtualization;

namespace Wpf_EF_Mvvm_sample
{
    /// <summary>
    ///
    /// Before running this proejct please create the required tables from the CreateAuthorBook.sql file
    /// and then update the connection string in app.config
    /// note:  if the following error message comes up after compiling the project
    ///
    /// >>Error 1 No connection string named 'AuthorBookEntities' could be found in the application config file.<<
    ///
    /// this error message can be ignored.  The project should run fine.
    /// </summary>
    class MainWindowViewModel : INotifyPropertyChanged
    {
        public class AuthorProvider : IPagedSourceProvider<Author>
        {
            private AuthorBookEntities _ctx = null;

            public AuthorProvider(AuthorBookEntities ctx)
            {
                _ctx = ctx;
            }

            public int Count
            {
                get { return (from a in _ctx.Authors select a).Count(); }
            }

            public PagedSourceItemsPacket<Author> GetItemsAt(int pageoffset, int count, bool usePlaceholder)
            {
                return new PagedSourceItemsPacket<Author>() { LoadedAt = DateTime.Now, Items = (from a in _ctx.Authors orderby a.AuthorName select a).Skip(pageoffset).Take(count) };
            }

            public int IndexOf(Author item)
            {
                return (from a in _ctx.Authors orderby a.AuthorName where a.AuthorName.CompareTo(item.AuthorName)<0 select a).Count();
            }

            public void OnReset(int count)
            {
            }
        }

        public class BookProvider : IPagedSourceProvider<Book>
        {
            private AuthorBookEntities _ctx = null;

            private Author _Author = null;
           
            public BookProvider(AuthorBookEntities ctx, Author author)
            {
                _ctx = ctx;
                _Author = author;
            }

            public int Count
            {
                get { return (from b in _ctx.Books where b.AuthorId == _Author.AuthorId orderby b.Title select b).Count(); }
            }

            public PagedSourceItemsPacket<Book> GetItemsAt(int pageoffset, int count, bool usePlaceholder)
            {
                return new PagedSourceItemsPacket<Book>() { LoadedAt = DateTime.Now, Items = (from b in _ctx.Books where b.AuthorId == _Author.AuthorId orderby b.Title select b).Skip(pageoffset).Take(count) };
            }

            public int IndexOf(Book item)
            {
                return (from b in _ctx.Books where b.AuthorId == _Author.AuthorId && b.Title.CompareTo(item.Title)<0 orderby b.Title select b).Count() ;
            }

            public void OnReset(int count)
            {
            }
        }

        AuthorBookEntities ctx = new AuthorBookEntities();

        public MainWindowViewModel()
        {
            FillAuthors();
        }

        private void FillAuthors()
        {
            Authors = new VirtualizingObservableCollection<Author>(new PaginationManager<Author>(new AuthorProvider(this.ctx)));
        }

        private VirtualizingObservableCollection<Author> _authors;
        public VirtualizingObservableCollection<Author> Authors
        {
            get
            {
                return _authors;
            }
            set
            {
                _authors = value;
                NotifyPropertyChanged();
            }
        }

        private Author _selectedAuthor;
        public Author SelectedAuthor
        {
            get
            {
                return _selectedAuthor;
            }
            set
            {
                _selectedAuthor = value;
                NotifyPropertyChanged();
                FillBook();
            }
        }

        private void FillBook()
        {
            Author author = this.SelectedAuthor;

            this.Books = new VirtualizingObservableCollection<Book>(new PaginationManager<Book>(new BookProvider(this.ctx, author)));
        }

        private VirtualizingObservableCollection<Book> _books = null;
        public VirtualizingObservableCollection<Book> Books
        {
            get
            {
                return _books;
            }
            set
            {
                _books = value;
                NotifyPropertyChanged();
            }
        }

        private Book _selectedBook;
        public Book SelectedBook
        {
            get
            {
                return _selectedBook;
            }
            set
            {
                _selectedBook = value;
                NotifyPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

Now, all we need to do is bootstrap the manger, so in MainWindow.xaml.cs, we extend the constructor as follows:

        public MainWindow()
        {
            if (!VirtualizationManager.IsInitialized)
            {
                VirtualizationManager.Instance.UIThreadExcecuteAction = (a) => Dispatcher.Invoke(a);
                new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Background, delegate(object s, EventArgs a) { VirtualizationManager.Instance.ProcessActions(); }, this.Dispatcher).Start();
            }

            InitializeComponent();
        }

 

Thats it - we are done ! now we can rest assured that the system we respond with similar performance if we have three or three hundred thousand authors / books.

Points of Interest

The VirtualizingObservableCollection can be found on nuget - See http://www.nuget.org/packages/VirtualizingObservableCollection/

Feel free to visit https://alphachitech.wordpress.com/ for more information and updates.

History

Initial version. Thanks Richard for providing a nice clean example like this !!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here