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
{
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 !!