這兩天在着實研究EF Core項目當中對於一些查詢也沒實際去檢測,因而想着利用放假時間去實際測試下,結果本文就出來了,too young,too simple,後續博主會從底層翻譯表達式樹弄起,來從源頭瞭解EF Core,經過本文你會明白不是EF Core團隊沒作性能優化,而是你根本就沒用過並且正在倒退。緩存
簡單粗暴直接上代碼,給出上下文以及須要用到的測試類,以下:性能優化
public class EFCoreContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer(@"Server=.;Database=EFCoreDb;Trusted_Connection=True;"); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>(pc => { pc.ToTable("Blog").HasKey(k => k.Id); pc.Property(p => p.Name).IsRequired(); pc.Property(p => p.Url).IsRequired(); pc.Property(p => p.Count).IsRequired(); pc.Property(p => p.RowVersion).IsRequired().IsRowVersion().ValueGeneratedOnAddOrUpdate(); }); } }
你是否像以下去獲取分頁數據呢,咱們來一塊兒瞧瞧:閉包
var ef = new EFCoreContext(); var blogs = ef.Blogs; var example1 = blogs .Skip(1) .Take(1) .ToList(); var example2 = blogs .Skip(10) .Take(10) .ToList();
咱們經過以下SQL語句來查看查詢計劃生成的SQL語句:ide
SELECT sys.syscacheobjects.cacheobjtype, sys.dm_exec_query_stats.execution_count, sys.syscacheobjects.SQL, sys.dm_exec_query_plan.query_plan FROM sys.dm_exec_query_stats INNER JOIN sys.dm_exec_cached_plans ON sys.dm_exec_cached_plans.plan_handle = sys.dm_exec_query_stats.plan_handle INNER JOIN sys.syscacheobjects ON sys.syscacheobjects.bucketid = sys.dm_exec_cached_plans.bucketid CROSS APPLY sys.dm_exec_query_plan(sys.dm_exec_query_stats.plan_handle)
結果以下:性能
咱們再來看看xml文件中生成的SQL語句是怎樣的。測試
這說明什麼問題呢,上述查詢計劃中生成的SQL語句對於咱們上述去取數據首選從第二條取一條,接下來是去取第十條後的十條,同時上述SQL語句而是聲明瞭兩個變量,換言之,上述兩條語句查詢最終在第一次查詢後SQL查詢計劃進行了緩存,下次再去取數據時直接調用此SQL語句以此達到重用的目的,下面要是咱們進行以下改造,結果會怎樣呢?優化
var ef = new EFCoreContext(); var blogs = ef.Blogs; var count = 1; var example1 = blogs .Skip(count) .Take(count) .ToList(); count = 10; var example2 = blogs .Skip(count) .Take(count) .ToList();
結果通過上述改造利用變量的形式和直接賦值的形式是一致的,沒有什麼可講的,下面咱們再來說述另一種狀況。請繼續往下看。ui
var ef = new EFCoreContext(); var blogs = ef.Blogs; var skipTakeWithInt1 = blogs .OrderBy(b => b.Id).Where(d => d.Name.Length > 1).ToList(); var skipTakeWithInt2 = blogs .OrderBy(b => b.Id).Where(d => d.Name.Length > 10).ToList();
看出什麼沒有,對於上述兩條查詢則是對應進行了兩次SQL查詢,這下意識到了其中玄機了吧,下面咱們將上述再改造一下:spa
var ef = new EFCoreContext(); var blogs = ef.Blogs; var length = 1; var skipTakeWithVariable1 = blogs .OrderBy(b => b.Id).Where(d => d.Name.Length > length).ToList(); length = 10; var skipTakeWithVariable2 = blogs .OrderBy(b => b.Id).Where(d => d.Name.Length > length).ToList();
咱們利用變量替換值改造後結果生成的SQL語句與上述大相徑庭,這裏一樣是聲明瞭長度的變量,你發現沒該變量竟然和咱們聲明的變量長度是一致的,有點神奇,如此這樣經過變量替換值得方式來達到SQL查詢計劃重用的目的,說到這裏,咱們是否是應該回答一下爲何會有這樣的狀況發生呢。不少東西當你一直沒用到時就以爲不會用到壓根不用學,其實否則,好比這樣,其本質究竟是怎樣的呢,實際上是由於前面我已經講過【閉包】的緣由。翻譯
lambda表達式對咱們聲明的變量進行了捕獲而後延長了其生命週期,也就是說將變量相似變成類中一個字段了,相似以下:
[CompilerGenerated] public sealed class ExampleClass1 { public int Length; } [CompilerGenerated] public sealed class ExampleClass2 { public int Length; }
自動編譯生成兩個類同時存在兩個對於長度的字段。接下來當咱們利用變量進行查詢就演變了以下這樣:
var ef = new EFCoreContext(); var blogs = ef.Blogs; var length = 1; var example1 = new ExampleClass1() { Length = length }; var skipTakeWithVariable1 = blogs .OrderBy(b => b.Id).Where(d => d.Name.Length > example1.Length).ToList(); length = 10; var example2 = new ExampleClass2() { Length = length }; var skipTakeWithVariable2 = blogs .OrderBy(b => b.Id).Where(d => d.Name.Length > example2.Length).ToList();
這樣就很明瞭了爲何經過變量達到查詢計劃重用的目的。
關於EntityFramework Core雖然目前設計的性能很是好,可是有些東西等咱們去用時還得多加驗證,看其背後的本質是怎樣的,才能不會心生疑竇,到底該怎樣用,如何用,心中要有定數纔是,一點一滴的細小優化不注意最終將致使大意失荊州。接下來博主會在三個方向不定時更新博客,第一個是SQL Server性能優化系列,第二個是ASP.NET Core MVC/WebAPi,第三個則是EntityFramework Core原理解析,敬請期待。