談談性能問題
EF Core 是一種快速而令人滿意的ORM 數據訪問框架,但隨着Web 應用程序越來越頻繁的訪問,性能變得越來越重要,但讓人詬病的性能問題一直是很多程序員熱聊的話題,實際情況並不是性能差,而是需要我們掌握如何規 避陷阱和避開影響性能的坑。
純手共執行一個 SQL 語句和用 ORM 框架性能基本相當,只要優化得好,性能問題可以忽略,一般大系統的性能問題都是數據庫設計問題,與使用何種數據訪問技術無直接關系,EF Core 中也可以直接執行 SQL 語句。
使用上下文實例池
services.AddDbContext<EmployeeContext>(options => options.UseSqlServer(connection)); services.AddDbContextPool<EmployeeContext>(options => options.UseSqlServer(connection));
如果使用 AddDbContextPool 方法,那么在控制器請求DbContext 實例時,我們會首先檢查池中有無可用的實例。 請求處理完成后,實例的任何狀態都將被重置,並且實例本身會返回池中。
從概念上講,此方法類似於ADO.NET 連接池的運行原理,並具有節約DbContext 實例初始化成本的優勢。
https://docs.microsoft.com/zh-cn/ef/core/what-is-new/ef-core-2.0#dbcontext-pooling
Use DbContextPooling to improve the performance: .Net Core 2.1 feature
顯式編譯查詢
EF 早期版本以及 LINQ to SQL 中已經提供手動或顯式編譯的查詢API,允許應用程序緩存查詢轉換,使其可僅被計算一次並執行多次。
盡管 EF Core 通常可基於查詢表達式的哈希表示法自動編譯和緩存查詢,但是使用此機制可繞過哈希計算和緩存查詢,允許應用程序通過調用委托來使用已編譯查詢,從而實現性能小幅提升。
// Create an explicitly compiled query private static Func<CustomerContext, int, Customer> _customerById = EF.CompileQuery((CustomerContext db, int id) => db.Customers .Include(c => c.Address) .Single(c => c.Id == id)); // Use the compiled query by invoking it using (var db = new CustomerContext()) { var customer = _customerById(db, 147); }
多活動結果集數據庫連接復用
多活動結果集 (MARS) 是一項允許對單個連接執行多個批處理的功能。 在以前的版本中,在單個連接上一次只能執行一個批處理。 使用 MARS 執行多個批處理並不意味着同時執行操作。
在連接字符串中添加:MultipleActiveResultSets=True 即可啟用 MARS 特性。
Multiple Active Result Sets (MARS)
如果未啟用 MARS 連接復用,多次打開應用程序,在 SQL Server Management Studio 中使用 sp_who 命令會查詢當前存在多個活動的連接。
如果啟用了 MARS 連接復用,進行上述操作,發現只有一個活動連接訪問數據庫,因為連接被復用。
優先使用Async 異步方法
EF Core 中提供了很多形如xxxAsync 的異步方法,推薦使用這些方法提高吞吐量和性能,減少不必要的延時等待。
批處理語句
optionbuilder.UseSqlServer(sConnString , b => b.MaxBatchSize(1)); //默認是批處理,這樣配置是關閉批處理 Entity Framework Core 批處理語句性能測試
添加索引提高數據查詢性能
索引是對數據庫表中一列或多列的值進行排序的一種結構,使用索引可快速訪問數據庫表中的特定信息。
modelBuilder.Entity<Blog>().HasIndex(b => b.Url).IsUnique(); modelBuilder.Entity<Person>() .HasIndex(p => new { p.FirstName, p.LastName });
避免N+1 查詢
模型中可使用導航屬性來加載相關實體。 有三種常見的 O/RM 模式可用於加載關聯數據。預先加載表示從數據庫中加載關聯數據,作為初始查詢的一部分。顯式加載表示稍后從數據庫中顯式加載關聯數據。延遲加載表示在訪問 導航屬性時,從數據庫中以透明方式加載關聯數據。
var blogs = context.Blogs.Include(blog => blog.Posts) .ThenInclude(post => post.Author).ToList();
但是,任何技術都有兩面性,優勢不可能讓你占完了,使用N+1 模式的優點是可以單獨緩存部分數據。
跟蹤與非跟蹤查詢
跟蹤行為決定了 EF Core 是否將有關實體實例的快照信息保留在其更改跟蹤器中。 如果已跟蹤某個實體,則該實體中檢測到的任何更改都會在SaveChanges() 期間永久保存到數據庫。
當決定只查詢數據,不更改數據時,非跟蹤查詢十分有用,非跟蹤查詢的執行會更快,因為無需為查詢實體設置快 照跟蹤信息。 如果不需要更新從數據庫中檢索到的實體,則應優先使用非跟蹤查詢。
var blogs = context.Blogs.AsNoTracking().ToList(); context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
關閉 DetectChanges 狀態同步
當從數據庫進行查詢數據時,上下文便捕獲了每個實體屬性的快照(數據庫值,原始值,當前值),當調用SaveChanges 時,在內部會自動調用 DetectChanges 方法,此方法將掃描上下文中所有實體,並比較當前屬性值和存儲在快照中的原始屬性值,如果被找到的屬性值發生了改變,此時EF將會與數據庫進行交互,進行數據更 新。
會導致自動調用 DetectChanges 方法: Find、Local、Remove、Add、Update、Attach、SaveChanges 和
Entry 等。
但是,自動同步狀態會頻繁調用,可手動關閉以上方法的自動同步,當數據都修改好后,一次性手動同步。
context.ChangeTracker.AutoDetectChangesEnabled = false; //執行操作后手動同步狀態 context.ChangeTracker.DetectChanges();
數據緩存
緩存可以減少重復內容的多次生成成本,從而顯著提高應用程序的性能和可伸縮性。 緩存最適用於不經常更改的數據,生成成本很高的數據。 通過緩存,可以比從數據源返回的數據的副本速度快得多。
EF Core 的高性能二級查詢緩存開源庫:EntityFrameworkCore.Cacheable
var cacheableQuery = cacheableContext.Books .Include(d => d.Pages) .ThenInclude(d => d.Lines) .Where(d => d.ID == 200) .Cacheable(TimeSpan.FromSeconds(60));
使用 EF.Functions.Link 模糊查詢
EF Core 支持使用 StartsWith、Contains 和 EndsWith 方法進行模糊查詢,這些方法被翻譯成 LIKE 語句,但為了提高性能也提供了一個EF.Functions.Like 方式,這種方式生成的 SQL 語句性能更優。
var result= context.Blogs .Where(b => EF.Functions.Like(b.BlogName, "%xcode%"))
Entity Framework Core Like 查詢揭秘
DbFunctionAttribute 標量函數
EF Core 支持映射數據庫中定義的函數,可以在LINQ 查詢中使用,該功能支持將數據庫標量函數映射到方法存根, 使其可用於LINQ 查詢並轉換為 SQL。
在 DbContext 上聲明靜態方法,並使用 DbFunctionAttribute 對其批注:
public class BloggingContext : DbContext { [DbFunction] public static int PostReadCount(int blogId) { throw new Exception(); } }
此類方法會自動注冊,對LINQ 查詢方法的調用可轉換為SQL 中的函數調用:
var query =context.Posts.Where(p=>BloggingContext.PostReadCount(p.Id) > 5)