Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / WPF
Alternative
Article

Tutorial for a Basic WPF – MVVM Project Using Entity Framework

Rate me:
Please Sign up or sign in to vote.
4.56/5 (6 votes)
17 Feb 2015CPOL3 min read 32.1K   856   31   6
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



Comments and Discussions

 
GeneralMy vote of 5 Pin
Thomas Maierhofer (Tom)18-Feb-15 17:51
Thomas Maierhofer (Tom)18-Feb-15 17:51 
QuestionStraight from VM into DB? Pin
Sacha Barber18-Feb-15 2:02
Sacha Barber18-Feb-15 2:02 
AnswerRe: Straight from VM into DB? Pin
Andrew Whiddett18-Feb-15 4:11
Andrew Whiddett18-Feb-15 4:11 
Questionsource code file link broken Pin
Tridip Bhattacharjee17-Feb-15 20:31
professionalTridip Bhattacharjee17-Feb-15 20:31 
AnswerRe: source code file link broken Pin
fredatcodeproject18-Feb-15 1:14
professionalfredatcodeproject18-Feb-15 1:14 
AnswerRe: source code file link broken Pin
Clemente Giorio30-Mar-15 0:59
Clemente Giorio30-Mar-15 0:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.