2012-04-13 38 views
1

我正在爲Windows Phone(SDK 7.1)應用程序編寫Silverlight,並且正在從Windows Phone Silverlight工具包的LongListSelector控件中顯示來自CompactSQL DB的數據。一旦列表變得大約150個項目,應用程序真的減緩加載數據,導航到頁面和動畫無法顯示(我知道使用後臺線程將有助於釋放UI線程的動畫)。將Linq2SQL簡單查詢轉換爲CompiledQueries以提高應用程序性能

我目前有三個查詢,我經常使用 - 每次從LongListSelector更新數據或頁面NavigatedTo。我已經將MoviesByTitle轉換爲CompiledQuery,這有很大的幫助,所以我期待爲我的其他兩個查詢(groupedMoviesLongListSelector.ItemSourceList<Group<Movie>>)做同樣的事情,但我似乎無法弄清楚正確的語法。

有關如何使這些查詢更有效的任何建議 - 通過使用CompiledQuery或其他方式?

MoviesByTitle是在稱爲Queries

public static Func<MovieDBContext, IOrderedQueryable<Movies>> MoviesByTitle = CompiledQuery.Compile((MovieDBContext db) => from m in db.Movies orderby m.Title,m.Year select m); 

字段中的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>>(); 

在的MainPage一個LOADDB()方法內的另一個類 - 調用此方法中的OnNavigatedTo並且在其它幾個地方時數據庫已更新。

//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(); 

GroupHeaderMovies在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 { } 
    } 

Group類的屬性和一個實體如下

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; 
    } 
    ... 
} 

回答

4

我假定GroupHeader是存儲在DB的實​​體與實體之間存在1-n的關係。

首先,我在這裏看不到3個DB查詢。 LINQ表達式並不總是一個數據庫查詢(例如,有對象的LINQ)。有時候確定真正發生的事情非常具有挑戰性。在這種情況下,最好的朋友是數據庫分析器工具或IntelliTrace - 它們顯示在運行時正在DB上執行的查詢。

據我瞭解代碼,你實際上有1+N查詢:第一個是MoviesByTitle,然後你有N查詢在表達式中獲取電影按其標題分組。這是N而不是1因爲你投queryIEnumerable<>使得它不再是一個查詢,但一個簡單的CLR對象,這簡直是在foreach般的循環將查詢發送到每個需要一個GroupHeader實體時間DB迭代(這是一個實體,不是嗎?)。

嘗試將2個查詢合併爲一個。你甚至可能不需要使用CompiledQuery。這裏是一個近似的代碼:

// I left this without changes 
if (emptyGroups.Count < 1)   
{   
    characters.ForEach(x => emptyGroups.Add(new Group<Movies>(x, new List<Movies>())));   
} 

// I put var here, but it actually is an IQueryable<Movie> 
var query = from m in App.db.Movies orderby m.Title, m.Year select m; 

// Here var is IQueryable<anonymous type>, I just can't use anything else but var here 
var groupedMoviesQuery = from t in query 
        group t by t.GroupHeader into grp 
        orderby grp.Key 
        select new 
        { 
         Movies = grp, 
         Header = grp.Key 
        }; 

// I use AsEnumerable to mark that what goes after AsEnumerable is to be executed on the client 
IEnumerable<Group<Movie>> groupedMovies = groupedMoviesQuery.AsEnumerable() 
              .Select(x => new Group<Movie>(x.Header, x.Movies)) 
              .ToList(); 

//No changes here 
moviesLongList.ItemsSource = (from t in groupedMovies.AsEnumerable().Union(emptyGroups)    
           orderby t.Title    
           select t).ToList(); 

此代碼應該工作的方式更好。我實際上做的是我將query從僅爲迭代的CLR對象IEnumerable變爲IQueryable,這可以進一步包裝成更復雜的查詢。現在只有一個查詢可以獲取按標題分組的所有電影。它應該很快。

我會介紹給代碼甚至更多的改進,使其更快的工作:

  1. 您使用來自DB和一些默認列表讀取實體Union。你之後訂購它。您可以安全地刪除Linq2Sql代碼中的所有其他排序('query'和'groupedMoviesQuery')
  2. 與連接相比,分組似乎不是最有效的。你爲什麼不直接詢問GroupHeader?包括他們的相關Movie?這應該在db查詢中產生一個JOIN,應該比GROUP BY更有效。
  3. 如果仍然存在性能問題,可以將查詢轉換爲已編譯的查詢。

,我會告訴編譯查詢的一個例子,你原有的邏輯與上面的列表中的第一個項目的優化:

class Result 
{ 
    public GroupHeader Header {get;set;} 
    public IEnumerable<Movie> Movies {get;set;} 
} 

public static Func<MovieDBContext, IQueryable<Result>> GetGroupHeadersWithMovies = 
    CompiledQuery.Compile((MovieDBContext x) => 
     from m in x.Movies 
     group m by m.GroupHeader into grp 
     select new Result 
     { 
     Movies = grp, 
     Header = grp.Key 
     }); 
+1

感謝這一切。特別是對於解釋 - 從長遠來看,知識將幫助我更直接的代碼。 我用秒錶來測試我的代碼,然後實施一些你需要優化的建議。 – Styff 2012-04-16 14:12:37