前言
這個問題從未遇見過,是一位前輩問我EF Core內存泄漏問題時我才去深入探討這個問題,剛開始我比較驚訝,居然還有這種問題,然后就有了本文,直接拿前輩的示例代碼並稍加修改成就了此文,希望對在自學EF Core過程中的童鞋能有些許幫助。
EntityFramework Core內存泄漏回顧
接下來我將用簡單示例代碼來還原整個造成EntityFramework Core內存泄漏的過程,同時在這個過程中您也可思考一下其中的原因和最終的結果是否一致。
public class TestA { public long Id { get; set; } public string Name { get; set; } }
public class EFCoreDbContext : DbContext { public EFCoreDbContext(DbContextOptions options) : base(options) { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=MemoryLeak;integrated security=True;MultipleActiveResultSets=True;"); base.OnConfiguring(optionsBuilder); } public DbSet<TestA> TestA { get; set; } }
public class TestUserCase { public void InvokeMethod(IServiceProvider serviceProvider) { EFCoreDbContext _context = null; if (_context == null) { _context = serviceProvider.GetRequiredService<EFCoreDbContext>(); } for (var i = 0; i < 10; i++) { var testA = _context.TestA.FirstOrDefault(); Console.WriteLine(i); } } }
如上是整個示例代碼,重頭戲來了,接下來我們在控制台中來通過依賴注入上下文,並獲取注入容器中的上下文並調用上述TestUserCase類中的方法,如下:
var services = new ServiceCollection(); services.AddDbContext<EFCoreDbContext>(options => options.UseSqlServer("data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=MemoryLeak;integrated security=True;MultipleActiveResultSets=True;")); var serviceProvider = services.BuildServiceProvider(); for (int i = 0; i < 1000; i++) { var test = new TestUserCase(); test.InvokeMethod(serviceProvider); }

通過上述測試內存基本在15兆左右,當然根據機器配置不同最終得到的結果有所差異,但是內存基本沒有什么大的波動,接下來我們來改造上述代碼。上述我們將serviceProvider通過方法傳遞到TestUserCase中的InvokeMethod方法中,為了簡便我們將獲取到的serviceProvider改造成靜態的,如下:
public static class ServiceLocator { private static IServiceCollection _services; public static IServiceProvider Instance { get { if (_services == null) return null; else return _services.BuildServiceProvider(); } } public static void Init(IServiceCollection services) { _services = services; } }
public class TestUserCase { public void InvokeMethod() { IServiceScope _serviceScope = null; EFCoreDbContext _context = null; if (_context == null) { _serviceScope = ServiceLocator.Instance.GetRequiredService<IServiceScopeFactory>().CreateScope(); _context = _serviceScope.ServiceProvider.GetRequiredService<EFCoreDbContext>(); } for (var i = 0; i < 10; i++) { var testA = _context.TestA.FirstOrDefault(); Console.WriteLine(i); } } }
var services = new ServiceCollection(); services.AddDbContext<EFCoreDbContext>(options => options.UseSqlServer("data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=MemoryLeak;integrated security=True;MultipleActiveResultSets=True;")); ServiceLocator.Init(services); for (int i = 0; i < 1000; i++) { var test = new TestUserCase(); test.InvokeMethod(); }
如上我們通過ServiceLocator類來構建serviceProvider,並將返回serviceProvider賦值給靜態變量,然后在我們調用的方法中直接獲取容器中的上下文,這樣就免去了傳遞的麻煩。

經過我們上述改造后最終運行內存達到了比較可怕的三百多兆,看來上下文壓根就沒進行GC,那我是不是改造成如下就可以了呢?
var scopeFactory = ServiceLocator.Instance.GetRequiredService<IServiceScopeFactory>(); using (var scope = scopeFactory.CreateScope()) using (var context = scope.ServiceProvider.GetRequiredService<EFCoreDbContext>()) { for (var i = 0; i < 10; i++) { var testA = context.TestA.FirstOrDefault(); Console.WriteLine(i); } }

原以為是上下文沒有及時得到釋放而導致內存激增,但是看到上述結果依然一樣沒有任何改變,問題是不是到此就結束了呢?下面我們改變注入上下文的方式看看,如下:
var services = new ServiceCollection(); var options = new DbContextOptionsBuilder<EFCoreDbContext>() .UseSqlServer("data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=MemoryLeak;integrated security=True;MultipleActiveResultSets=True;") .Options; services.AddScoped(s => new EFCoreDbContext(options));

當我們改變了注入上下文方式后發現此時不會造成內存泄漏,也就是說上下文得到了GC,無論是我是否是手動釋放上下文即通過Using包括或者不包括都不會出現內存泄漏問題。通過注入方式不同得到的結果截然不同,但是在我們的理解中通過AddDbContext注入上下文中的第二個參數是默認為Scope,那和我們通過AddScoped注入上下文應該是一樣對不對,那為何結果又不同呢?豈不是沖突了嗎?在Web不會出現這樣的問題,未深入研究,我猜測其原因可能如下:
通過AddDbContext注入上下文只適用於Web應用程序即只對Web應用程序有效而對控制台程序可能無效,同時在Web應用程序中AddDbContext注入上下文和AddScoped注入上下文一致,而對於控制台程序存在不一致問題。一言以蔽之,在Web和Console中通過AddDbContext注入上下文可能存在處理機制不同。
總結
不知如上淺薄分析是否有漏洞或者說代碼有錯誤的地方,期待看到本文的您能有更深入的見解可留下您的評論,如果結果是這樣不建議在控制台中使用AddDbContext方法注入上下文。
