简体   繁体   English

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

[英]Converting Linq2SQL simple queries to CompiledQueries to increase app performance

I am writing a Silverlight for Windows Phone (SDK 7.1) app and I am displaying data from a CompactSQL DB in a LongListSelector control from the Silverlight Toolkit for Windows Phone. 我正在为Windows Phone(SDK 7.1)应用程序编写Silverlight,我正在使用Silverlight Toolkit for Windows Phone的LongListSelector控件中的CompactSQL DB显示数据。

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

I currently have three queries that I use constantly - everytime the data from LongListSelector is updated or the page is NavigatedTo. 我目前有三个不断使用的查询 - 每次更新LongListSelector的数据或页面为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. 我已将MoviesByTitle转换为CompiledQuery并且已经帮助了很多,所以我希望对我的其他两个查询( groupedMovies和类型为List<Group<Movie>> LongListSelector.ItemSource做同样的事情,但我似乎无法弄清楚正确的语法。

Any suggestions on how I might make these queries more efficient - Through the use of CompiledQuery or otherwise? 关于如何提高这些查询效率的任何建议 - 通过使用CompiledQuery或其他方式?

MoviesByTitle is in another Class called Queries 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);

Fields in MainPage 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. 在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 is a property of Movies and an entity in the DB 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 { }
    }

The Group class is as follows 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;
    }
    ...
}

I assume that GroupHeader is an entity stored in the DB with 1-n relationship to the Movie entity. 我假设GroupHeader是存储在DB中的实体,与Movie实体的关系为1-n。

First of all, I don't see 3 DB queries here. 首先,我在这里看不到3个DB查询。 A LINQ expression is not always a DB query (eg there's LINQ to Objects). LINQ表达式并不总是数据库查询(例如,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. 在这种情况下,最好的朋友是数据库分析器工具或IntelliTrace - 它们显示在运行时在DB上执行的查询。

As far as I understand the code, you actually have 1+N queries: the first is MoviesByTitle , and then you have N queries in the expression that gets the movies grouped by their headers. 据我了解代码,你实际上有1+N查询:第一个是MoviesByTitle ,然后你在表达式中有N查询来获取按标题分组的电影。 It's N instead of 1 because you cast query to IEnumerable<> which makes it no longer a query but a simple CLR object , which is simply iterated in a foreach -like loop sending queries to the DB each time it needs a GroupHeader entity (it's an entity, isn't it?). 它是N而不是1因为你将queryIEnumerable<> ,这使得它不再是一个查询而是一个简单的CLR对象 ,它只是在类似foreach的循环中迭代,每次需要一个GroupHeader实体时向DB发送查询(它是一个实体,不是吗?)。

Try to combine 2 queries into one. 尝试将2个查询合并为一个。 You might even not need to use CompiledQuery . 您甚至可能不需要使用CompiledQuery Here's an approximate code: 这是一个近似的代码:

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

This code should work way better. 此代码应该更好地工作。 What I actually did is that I turned your query from IEnumerable which is an iterate-only CLR object to an IQueryable which can be further wrapped into a more complex query. 我实际上做的是我将你的queryIEnumerable (一个只有迭代的CLR对象)转换为一个IQueryable ,它可以进一步包装成一个更复杂的查询。 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: 我将对代码进行更多改进,以使其更快地工作:

  1. You use Union of entities read from the DB and of some default list. 您使用从DB读取的实体Union和一些默认列表。 You order it afterwards. 你事后订购了。 You can safely remove all other orderings from your Linq2Sql code ('query' and 'groupedMoviesQuery') 您可以安全地从Linq2Sql代码中删除所有其他排序('query'和'groupedMoviesQuery')
  2. Grouping seems to be not the most efficient here as compared to a join. 与联接相比,分组似乎不是最有效的。 Why don't you just query GroupHeader s including their related Movie s? 你为什么不查询GroupHeader包括他们相关的Movie That should produce a JOIN in the db query which should be more efficient than a GROUP BY. 这应该在db查询中产生一个JOIN,它应该比GROUP BY更有效。
  3. If you still have issues with performance, you can turn the query into a compiled query. 如果仍然遇到性能问题,可以将查询转换为已编译的查询。

I'll show an example of a compiled query for you original logic with optimization of the first item of the list above: 我将通过优化上面列表的第一项来显示原始逻辑的编译查询示例:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM