I am writing a Silverlight for Windows Phone (SDK 7.1) app and I am displaying data from a CompactSQL DB in a LongListSelectorcontrol from the Silverlight Toolkit for Windows Phone.
Once the list becomes about 150 items long, The app really slows down loading data, navigating to and from pages and animations fail to display (I know using a background thread would help with freeing up the UI thread for animations).
I currently have three queries that I use constantly – everytime the data from LongListSelector is updated or the page is NavigatedTo. I have converted MoviesByTitle into a CompiledQuery and that has helped quite a lot, so I was looking to do the same for my other two queries(groupedMovies and LongListSelector.ItemSource of type List<Group<Movie>>), however I cannot seem to figure out the correct syntax.
Any suggestions on how I might make these queries more efficient – Through the use of CompiledQuery or otherwise?
MoviesByTitle is in another Class called Queries
public static Func<MovieDBContext, IOrderedQueryable<Movies>> MoviesByTitle = CompiledQuery.Compile((MovieDBContext db) => from m in db.Movies orderby m.Title,m.Year select m);
Fields in MainPage
private static List<String> characters = new List<String> { "#", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" };
public static List<Group<Movies>> emptyGroups = new List<Group<Movies>>();
inside a LoadDB() method in MainPage – this method is called in OnNavigatedTo and in several other places when the DB is updated.
//Populates the 'empty' Groups of Movies objects only once.
if (emptyGroups.Count < 1)
{
characters.ForEach(x => emptyGroups.Add(new Group<Movies>(x, new List<Movies>())));
}
IEnumerable<Movies> query = Queries.MoviesByTitle(App.db);
//Groups the objects
IEnumerable<Group<Movies>> groupedMovies = (from t in query
group t by t.GroupHeader into grp
orderby grp.Key
select new Group<Movies>(grp.Key.ToString(), grp));
//Joins the 'empty' group and groupedMovies together for the LongListSelector control
moviesLongList.ItemsSource = (from t in groupedMovies.AsEnumerable().Union(emptyGroups)
orderby t.Title
select t).ToList();
GroupHeader is a property of Movies and an entity in the DB
[Column(CanBeNull=true, UpdateCheck = UpdateCheck.Never)]
public char GroupHeader
{
get
{
char l;
//For Alphabetized Grouping, titles do NOT start with "The ..."
if (this.Title.ToLowerInvariant().StartsWith("the "))
{
l = this.Title.ToLowerInvariant()[4];
}
else
{
l = this.Title.ToLower()[0];
}
if (l >= 'a' && l <= 'z')
{
return l;
}
return '#';
}
set { }
}
The Group class is as follows
public class Group<T> : IEnumerable<T>
{
public Group(string name, IEnumerable<T> items)
{
this.Title = name;
this.Items = new List<T>(items);
}
public string Title
{
get;
set;
}
public IList<T> Items
{
get;
set;
}
...
}
I assume that
GroupHeaderis an entity stored in the DB with 1-n relationship to theMovieentity.First of all, I don’t see 3 DB queries here. A LINQ expression is not always a DB query (e.g. there’s LINQ to Objects). Sometimes determining what really is going on is quite challenging. The best friend in such cases is a DB profiler tool or the IntelliTrace – they show what queries are being executed on the DB at run time.
As far as I understand the code, you actually have
1+Nqueries: the first isMoviesByTitle, and then you haveNqueries in the expression that gets the movies grouped by their headers. It’sNinstead of1because you castquerytoIEnumerable<>which makes it no longer a query but a simple CLR object, which is simply iterated in aforeach-like loop sending queries to the DB each time it needs aGroupHeaderentity (it’s an entity, isn’t it?).Try to combine 2 queries into one. You might even not need to use
CompiledQuery. Here’s an approximate code:This code should work way better. What I actually did is that I turned your
queryfromIEnumerablewhich is an iterate-only CLR object to anIQueryablewhich can be further wrapped into a more complex query. Now there’s only one query which gets all movies grouped by headers. It should be fast.I would introduce even more improvements to the code to make it work even faster:
Unionof entities read from the DB and of some default list. You order it afterwards. You can safely remove all other orderings from your Linq2Sql code (‘query’ and ‘groupedMoviesQuery’)GroupHeaders including their relatedMovies? That should produce a JOIN in the db query which should be more efficient than a GROUP BY.I’ll show an example of a compiled query for you original logic with optimization of the first item of the list above: