前言
EntityFramework Core 2.0引入了顯式編譯查詢,在查詢數據時預先編譯好LINQ查詢便于在請求數據時能夠立即響應。顯式編譯查詢提供了高可用場景,通過使用顯式編譯的查詢可以提高查詢性能。EF Core已經使用查詢表達式的散列來表示自動編譯和緩存查詢,當我們的代碼需要重用以前執行的查詢時,EF Core將使用哈希查找并從緩存中返回已編譯的查詢。我們更希望直接使用編譯查詢繞過散列計算和高速緩存查找。
EntityFramework Core 2.0顯式編譯查詢
比如我們要從博客實體中通過主鍵查詢博客同時饑餓加載發表文章的集合列表,如下:
var id = 1;using (var context = new EFCoreDbContext()){var blog = context.Blogs.AsNoTracking().Include(c => c.Posts).Where(c => c.Id == id).FirstOrDefault();}
當進行上述查詢時,此時要經過編譯翻譯階段最終返回實際結果,比如在Web網站上這樣的請求很頻繁,此時將嚴重影響響應速度導致頁面加載數據過慢。從Web程序應用角度來看我們大可利用ASP.NET Core中的響應式緩存,在實際應用中我們會將查詢封裝為方法來使用,我們無法優化結果和查詢方式,但是我們能夠通過編譯查詢來提前保存好數據以達到緩存的效果。通過EF靜態類中的擴展方法CompileQuery來實現。如下:
static async Task<Blog> GetBlogAsync(EFCoreDbContext context, int id){Func<EFCoreDbContext, int, Task<Blog>> blog = EF.CompileAsyncQuery((EFCoreDbContext context, int Id) =>context.Blogs.Include(c => c.Posts).Where(c => c.Id == Id).FirstOrDefault());return await blog(context, id);}
常規查詢和顯式編譯查詢性能比較
接下來我們測試常規查詢和使用顯式編譯查詢的性能,我們利用EF Core提供的內存數據庫來測試避免使用SQL Server數據庫,利用SQL Server數據庫很難去比較二者性能問題,因為數據庫會進行查詢計劃優化和緩存,利用內存數據庫只知道當前執行的查詢不會進行任何優化, 首先我們下載EF Core內存數據庫。額外再說明一點內存數據庫在進行單元測試時很有意義。
接下來我們首先測試常規查詢,我們預先在內存數據庫中創建50條記錄,然后查詢十萬次數據,這樣來看每一次查詢都會再次重新編譯。
public static void Main(string[] args){var options = new DbContextOptionsBuilder<EFCoreDbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;var context = new EFCoreDbContext(options);var stopWatch = new Stopwatch();FillBlogs(context);stopWatch.Start();for (var i = 0; i < 1000000; i++){GetUnCompileQueryBlog(context);}stopWatch.Stop();Console.Write("Compiling time:");Console.WriteLine(stopWatch.Elapsed);Console.ReadKey();}static void FillBlogs(EFCoreDbContext context){for (var i = 0; i < 50; i++){context.Blogs.Add(new Blog{Name = "Jeffcky",CreatedTime = DateTime.Now,Url = "http://www.cnblogs/com/CreateMyself",ModifiedTime = DateTime.Now,Posts = new List<Post>(){new Post(){CommentCount = i, CreatedTime = DateTime.Now,ModifiedTime = DateTime.Now, Name = "EF Core"}}});}context.SaveChanges(true);}static Blog GetUnCompileQueryBlog(EFCoreDbContext context){return context.Blogs.Include(c => c.Posts).OrderBy(o => o.Id).FirstOrDefault();}
我們看到上述利用常規查詢總耗時27秒,接下來我們再來看看顯式編譯查詢耗時情況。
private static Func<EFCoreDbContext, Blog> _getCompiledBlog = EF.CompileQuery((EFCoreDbContext context) =>context.Blogs.Include(c => c.Posts).OrderBy(o => o.Id).FirstOrDefault());
var options = new DbContextOptionsBuilder<EFCoreDbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;var context = new EFCoreDbContext(options);var stopWatch = new Stopwatch();FillBlogs(context);stopWatch.Start();for (var i = 0; i < 100000; i++){GetCompileQueryBlog(context);}stopWatch.Stop();Console.Write("Compiling time:");Console.WriteLine(stopWatch.Elapsed);Console.ReadKey();
如上通過顯式編譯查詢耗時16秒,那么是不是就說明顯式編譯查詢性能一定優于常規查詢呢?顯然不是這樣,上述只是簡單的測試方法,有可能運行多次顯式編譯查詢性能還低于常規查詢,所以上述簡單的測試方法并不能看出常規查詢和顯式編譯查詢之間的性能差異,當查詢基數足夠大時則能通過機器明顯看出二者之間的性能差異,這也就說明了為什么EntityFramework Core官方文檔說明顯式編譯查詢的高可用。但是顯式編譯查詢還有且缺點,當我們進行如下查詢呢?
public static void Main(string[] args){var options = new DbContextOptionsBuilder<EFCoreDbContext>().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options;var context = new EFCoreDbContext(options);var blogs = GetCompileQueryBlogs(context);Console.ReadKey();}static Blog[] GetCompileQueryBlogs(EFCoreDbContext context){Func<EFCoreDbContext, Blog[]> func = EF.CompileQuery((EFCoreDbContext db) =>db.Blogs.Include(c => c.Posts).OrderBy(o => o.Id).ToArray());return func(context);}}
當前EntityFramework Core 2.0.1版本對于顯式編譯查詢還不支持返回IEnumerable<T>, IQueryable<T>的集合類型,期待未來能夠有所支持。
總結缺陷
顯式編譯查詢提供高可用場景,但是仍然存在其缺陷,期待未來能有更多支持,希望給閱讀的您一點幫助。精簡的內容,簡單的講解,希望對閱讀的您有所幫助,我們明天再會。