将Linq2SQL简单查询转换为CompiledQueries以提高应用程序性能

时间:2012-04-13 03:56:02

标签: c# silverlight windows-phone-7 linq-to-sql compiled-query

我正在为Windows Phone(SDK 7.1)应用编写Silverlight,我正在使用Silverlight Toolkit for Windows Phone的LongListSelector控件中的CompactSQL DB显示数据。

一旦列表变成大约150个项目,该应用程序真的减慢了加载数据,导航到页面和从页面导航和动画无法显示(我知道使用后台线程将有助于释放动画的UI线程)。 / p>

我目前有三个不断使用的查询 - 每次更新LongListSelector的数据或页面为NavigatedTo。我已将MoviesByTitle转换为CompiledQuery并且已经帮助了很多,所以我希望对我的其他两个查询(groupedMoviesLongListSelector.ItemSource类型执行相同操作List<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;
    }
    ...
}

1 个答案:

答案 0 :(得分:4)

我假设GroupHeader是存储在数据库中的实体,与Movie实体的关系为1-n。

首先,我在这里看不到3个DB查询。 LINQ表达式并不总是DB查询(例如,LINQ to Objects)。有时确定真正发生的事情是非常具有挑战性的。在这种情况下,最好的朋友是DB分析器工具或IntelliTrace - 它们显示在运行时在DB上执行的查询。

据我了解代码,您实际上有1+N个查询:第一个是MoviesByTitle,然后您在表达式中有N个查询,这些查询按照头。它是N而非1,因为您将query投射到IEnumerable<>使其不再是查询而是简单的CLR对象,这只是迭代在一个foreach类似的循环中,每次需要一个GroupHeader实体时,都会向数据库发送查询(它是一个实体,不是吗?)。

尝试将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(); 

此代码应该更好地工作。我实际上做的是将queryIEnumerable转变为IQueryable,这是一个仅迭代的CLR对象,可以进一步包装成更复杂的查询。现在只有一个查询可以按标题分组所有电影。它应该很快。

我会对代码进行更多改进,以使其更快地运行:

  1. 您使用从数据库读取的Union个实体和一些默认列表。你事后订购了。您可以安全地从Linq2Sql代码中删除所有其他排序('query'和'groupedMoviesQuery')
  2. 与联接相比,分组似乎不是最有效的。为什么不查询GroupHeader s包括相关的Movie?这应该在db查询中产生一个JOIN,它应该比GROUP BY更有效。
  3. 如果您仍然遇到性能问题,可以将查询转换为已编译的查询。
  4. 我将展示一个针对原始逻辑的编译查询示例,并优化上面列表的第一项:

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