徹底征服 Entity Framework Core 優化

徹底征服 Entity Framework Core 優化

徹底征服 Entity Framework Core 優化

作者 | 喵叔

出品 | CSDN(ID:CSDNnews)

這篇文章我們來講解一下 Entity Framework Core 的優化方案。Entity Framework Core 是微軟針對跨平臺開發推出的 ORM 框架,繼承了 Entity Framework 的眾多優點,也對 Entity Framework 中的不足進行了優化和補充。

雖然 Entity Framework Core 進行了性能上的優化,但是這些在進行大量數據庫操作的時候依然存在性能問題。

針對Entity Framework Core 的性能優化方案,不僅可以使用 Entity Framework 大部分的優化方案,還有一套專門針對 Entity Framework Core 的優化方案。 現在我們就來具體講解一下針對 Entity Framework Core 的優化方案。

彻底征服 Entity Framework Core 优化

零、禁用實體追蹤

當我們從數據庫中查詢出數據時,上下文就會創建實體快照,從而追蹤實體。在調用 SaveChanges 時,實體有任何更改都會保存到數據庫中。

但是當我們只需要查詢出實體而不需要修改時(只讀),實體追蹤就沒有任何用途了。這時我們就可以調用 AsNoTracking 獲取非追蹤的數據,這樣可以提高查詢性能。具體代碼如下:

`using (var db = new EFCDbContext)

{

var users = db.Users.AsNoTracking.ToList;

}

Entity Framework Core 默認使用的是快照式便跟追蹤,因此我們可以通過 ChangeTracker 來關閉 DetectChanges 來提高性能。我們來看一下具體的例子:

public override int SaveChanges(bool acceptAllChangeOnSuccess)

{

ChangeTracker.DetectChanges;

foreach (var entry in ChangeTracker.Entries.Where(p=>p.State==EntityState.Added))

{

this.AddRange(entry.Entity);

}

ChangeTracker.AutoDetectChangesEnabled = false;

var result = base.SaveChanges(acceptAllChangeOnSuccess);

ChangeTracker.AutoDetectChangesEnabled = true;

return result;

}

上述代碼,我們重寫了 SaveChanges 方法,通過 ChangeTracker.AutoDetectChangesEnabled = false;代碼關閉了變更追蹤,然後調用 Entity Framework Core 的 SaveChanges 方法保存數據,最後再次調用 ChangeTracker.AutoDetectChangesEnabled = true; 來開啟變更追蹤。這樣一來 Entity Framework Core 的最終性能得到了優化。

下面我們來思考一個問題,當需要多表關聯查詢的時候,我們應該怎麼優化查詢性能?

這時你一定會想到使用前面所說的 AsNoTracking 方法,那麼我們就把你想到的這個方法以代碼的形式展示出來:

using (var db = new EFCDbContext)

{

var user = from u in db.Users.AsNoTracking

join o in db.Orders.AsNoTracking

on u.Id equals o.UserId

select u;

}

看到上述代碼,你第一感受是什麼?每個表都要寫一個 AsNoTracking 方法,很麻煩吧?代碼很長吧?可讀性很低吧?

那麼怎麼來解決呢?Entity Framework Core 給我們提供了一個很好的就覺方案,就是通過上下問設置跟蹤行為為 AsNoTracking 。

using (var db = new EFCDbContext)

{

db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;

var users = db.Users;

var orders = db.Orders;

var user = from u in Users

join o in orders

on u.Id equals o.UserId

select u;

}

彻底征服 Entity Framework Core 优化

優化模糊查詢

模糊查詢在開發過程中經常用到,比如查詢姓名、電話號碼、郵箱等等都會用到模糊查詢。

我們也知道模糊查詢會造成全表掃描的問題,因此 Entity Framework Core 專門針對模糊查詢進行了一番優化,出現了 EF.Functions.Like 方法。我們可以利用這個特性來自定義模糊查詢。Like 方法有兩個重載:

1. 自定義匹配模式

舉個例子來講解這個重載的使用方法,例如我們需要查詢出姓名中包含 燕 的人員,我們可以這麼寫:

using (var db = new EFCDbContext)

{

var users =db.Users;

var user = users.Where(p=>EF.Functions.Like(p.Name,"%燕%")).ToList;

}

2. 將轉義字符當作普通字符

當我們傳遞的查詢參數包含轉義字符時,我們可以使用這個重載來將轉義字符轉換為普通字符來處理:

`using (var db = new EFCDbContext)

{

var users =db.Users;

var user = users.Where(p=>EF.Functions.Like(p.Name,@"%\\燕%",@"\\")).ToList;

}

彻底征服 Entity Framework Core 优化

自定義標量函數

Entity Framework Core 有一個重要特性就是自定義標量函數。

自定義標量函數可以將數據庫中的標量函數映射到類中的方法,並且在使用 LINQ 查詢時會用到。

自定義標量函數為我們提供了一個快捷創建方法,並在方法上應用 DbFunctionAttribute 屬性。

DbFunctionAttribute 屬性可以將靜態方法映射到數據庫函數。

默認情況下數據庫函數中的靜態方法名必須相同,我們也可以通過 DbFunctionAttribute 屬性來指定不同的名稱。在創建自定義標量函數的時候我們必須遵循如下兩個要求:

1. 函數必須是靜態方法,而且在上下文中聲明;

2. 只能作為參數標量值返回。

下面我們來看一下如何在上下文中定義標量函數:

[DbFunction(FunctionName="DbFunction",Schema="dbo")]

public static string MyFunction(string name)

{

//more code

}

上述代碼中我們定義了 MyFunction 方法映射進數據庫中的標量函數名稱 DbFunction,並且也定義了使用數據庫的架構名稱 dbo。

如果自定義標量函數方法沒有在上下文中定義,我們還可以在 OnCinfiguring 方法中利用 HasDbFunction 方法通過反射獲取自定義標量函數。我們將上面的代碼改造一下來看看:

modelBuilder.HasDbFunction(this.GetType.GetMeth("MyFunction"),options=>

{

options.HasName("DbFunction");

options.HasSchema("dbo");

})

注意:函數使用數據庫架構的名稱是必須存在的,否則將會拋出異常。

講了這麼多我們來看一下自定義標量函數到底該怎麼優化性能。我們都知道 Entity Framework Core 不支持將 LINQ 中的 Min、Max、Average 等函數翻譯成SQL查詢,只會在本地執行查詢,我們可以通過自定義標量函數來使 Min、Max、Average 等函數在遠端執行查詢。下面我們通過例子來看一下:

首先定義自定義標量函數:

public static class FunctionDemoClass

{

public static void DemoAverage(this EFCDbContext db)

{

using(var transaction = db.Database.BeginTransaction)

{

try

{

db.Database.ExecuteSqlCommand("IF OBJECT ID ('dbo.DemoAverage',N'FN') IS NOT DROP FUNCTION db0.DemoAverage");

db.Database.ExecuteSqlCommand("CREATE FUNCTION DemoAverage (@UserId int) RETURNS FLOAT "+

@"AS BEGIN

DECLARE @result AS FLOAT

SELECT @result AVG(CAST(ScoreAvg AS FLOAT)) FROM db.Score as s WHERE s.UserId=@UserId

RETURN @result END");

transaction.Commit;

}

catch(Exception ex)

{

throw ex;

}

}

}

}

接著我們在上下文中進行映射:

`public static float? DemoAverage(int userId)

{

// more code

}

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

modelBuilder.HasDbFunction(=>DemoAverage(default(int).HasSchema(dbo)));

base.OnModelCreating(modelBuilder);

}

通過這種方法我們將計算平均值的工作交給了數據庫,這樣就可以提高 Entity Framework Core 的查詢速度。

彻底征服 Entity Framework Core 优化

顯式編譯查詢

顯式編譯查詢也是 Entity Framework Core 的重要特性,主要用在提供高可用的場景下。

默認情況下 Entity Framework Core 使用查詢表達式的散列來表示自動編譯和緩存查詢,如果代碼需要重用 Entity Framework Core 將會使用用哈希查找從緩存中返回已編譯的查詢。

但是散列計算和高速緩存查找也會帶來性能問題,這時我們就需要拋棄散列計算和高速緩存查找。

Entity Framework Core 已經為我們想到了這一點,我們只需要調用 Entity Framework Core 靜態類中使用以下方法即可:

  • EF.CompileQuery

  • EF.CompileAsyncQuery

上述方法的第一個參數必須是上下文,第二個參數類型不限。小提示:上述兩個方法的第二個參數數量一共有8個。

我們依然通過例子來講解一下:

using (var _db = new EFCDbContext)

{

var query = EF.CompileQuery(

(EFCDbContext db ,int id)=>db.Users.FirstOrDefault(p=>p.Id==id)

);

User user1 = query(_db,123);

User user2 = query(_db,123);

}

上述代碼知識展示了顯示編譯查詢的使用方法,這段代碼並不能真實的反映出顯示編譯查詢的優點。

當我們同時進行上萬次查詢的時候就會發現 Entity Framework Core 的性能有顯著提升。因此當查詢頻繁訪問時,我們就可以使用顯示編譯查詢來提高查詢性能。小提示:顯示編譯查詢的執行速度是常規模式的2倍。

彻底征服 Entity Framework Core 优化

上下文實例池

Entity Framework Core 增加了上下文連接池的概念,可以在依賴注入中註冊 DbContext 來創建一個可重用的 DbContext 實例池。

也就是說每次請求都會從實例池中提取一個實例,而不是重新創建一個實例,這樣有利於程序性能的提高。默認情況下上下文實例池有128個實例,但是我們可以通過配置來改變默認值。

我們可以使用 AddDbContextPool 方法在依賴注入中註冊 DbContext 類。方法接收 DbContextOptionBuilder 用於定義鏈接字符串,第二個參數時實例池最大的實例數值。我們需要在 Startup 類的 ConfigureServices 方法中定義。

代碼如下:

public void ConfigureServices(IServiceCollection services)

{

var conStr="連接字符串";

services.AddDbContextPool<efcdbcontext>(options=>{/<efcdbcontext>

options.UseSqlServer(conStr,p=>p.MigrationAssembly(this.GetType.GetTypeInfo.Assembly.FullName));

},1000);

}

上述代碼中我們定義的上下文實力數量最大時1000,當請求數量超過1000時,Entity Framework Core 將會為後續的請求創建新的上下文實例,而不是從實例池中獲取。

因此設置最大實力數量只是限制了 Entity Framework Core 實例池中實例的數量,而不是限制上下文實例的總數。

彻底征服 Entity Framework Core 优化

總結

上述幾方面就是針對 Entity Framework Core 的性能優化,我不建議使用上下文實例池的方式,就如同我結尾所說的那樣最大實力數量只不過是限制了實力池中實例的數量,一旦請求超過實力池中最大的實力數量,將會創建新的實例,而不是排隊等候。

作者簡介:朱鋼,筆名喵叔,CSDN博客專家,.NET高級開發工程師,7年一線開發經驗,參與過電子政務系統和AI客服系統的開發,以及互聯網招聘網站的架構設計,目前就職於北京恆創融慧科技發展有限公司,從事企業級安全監控系統的開發。

【END】


分享到:


相關文章: