将 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
,这有很大帮助,因此我希望对我的其他两个查询(groupedMovies
和List<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();
GroupHeader
是 Movies
的属性,也是数据库中的实体
[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;
}
...
}
我假设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
。现在只有一个查询可以获取按标题分组的所有电影。它应该很快。
我会对代码进行更多改进,以使其工作得更快:
- 您可以使用从数据库读取的实体和某些默认列表
Union
。之后你订购它。您可以安全地从 Linq2Sql 代码中删除所有其他排序("query"和"groupedMoviesQuery") - 与联接相比,分组在这里似乎不是最有效的。您为什么不只查询
GroupHeader
,包括其相关的Movie
?这应该在数据库查询中生成一个 JOIN,它应该比 GROUP BY 更有效。 - 如果仍然存在性能问题,可以将查询转换为已编译的查询。
我将向您展示一个编译查询的示例,其中包含对上面列表的第一项的优化:
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
});