这个问题从未碰见过,是一位前辈问我EF Core内存泄漏问题时我才去深刻探讨这个问题,刚开始我比较惊讶,竟然还有这种问题,而后就有了本文,直接拿前辈的示例代码并稍加修改为就了此文,但愿对在自学EF Core过程当中的童鞋能有些许帮助。ide
接下来我将用简单示例代码来还原整个形成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类中的方法,以下:ui
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改形成静态的,以下:spa
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赋值给静态变量,而后在咱们调用的方法中直接获取容器中的上下文,这样就免去了传递的麻烦。code
通过咱们上述改造后最终运行内存达到了比较可怕的三百多兆,看来上下文压根就没进行GC,那我是否是改形成以下就能够了呢?blog
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); } }
原觉得是上下文没有及时获得释放而致使内存激增,可是看到上述结果依然同样没有任何改变,问题是否是到此就结束了呢?下面咱们改变注入上下文的方式看看,以下:ip
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注入上下文可能存在处理机制不一样。get
不知如上浅薄分析是否有漏洞或者说代码有错误的地方,期待看到本文的您能有更深刻的看法可留下您的评论,若是结果是这样不建议在控制台中使用AddDbContext方法注入上下文。string