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

本文关键字:高应用 应用 性能 简单 Linq2SQL 查询 转换 CompiledQuery | 更新日期: 2023-09-27 18:30:53

我正在编写一个 Silverlight for Windows Phone (SDK 7.1) 应用程序,我正在 Silverlight Toolkit for Windows Phone 的LongListSelector控件中显示来自 CompactSQL DB 的数据。

一旦列表变得大约 150 个项目长,该应用程序确实会减慢加载数据的速度,在页面和动画之间导航和动画无法显示(我知道使用后台线程将有助于释放动画的 UI 线程)。

我目前有三个经常使用的查询 - 每次更新来自 LongListSelector 的数据或页面被导航到时。我已经将MoviesByTitle转换为CompiledQuery,这有很大帮助,因此我希望对我的其他两个查询(groupedMoviesList<Group<Movie>>类型的LongListSelector.ItemSource)做同样的事情,但是我似乎无法找出正确的语法。

关于如何使这些查询更有效率的任何建议 - 通过使用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);

主页中的字段

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 的属性,也是数据库中的实体

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

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

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

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

据我了解代码,您实际上有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(); 

这段代码应该工作得更好。我实际所做的是,我将query从仅迭代 CLR 对象的 IEnumerable 转换为可以进一步包装到更复杂的查询中的IQueryable。现在只有一个查询可以获取按标题分组的所有电影。它应该很快。

我会对代码进行更多改进,以使其工作得更快:

  1. 您可以使用从数据库读取的实体和某些默认列表Union。之后你订购它。您可以安全地从 Linq2Sql 代码中删除所有其他排序("query"和"groupedMoviesQuery")
  2. 与联接相比,分组在这里似乎不是最有效的。您为什么不只查询GroupHeader,包括其相关的Movie?这应该在数据库查询中生成一个 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
      });