为了账号安全,请及时绑定邮箱和手机立即绑定

EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下?

标签:
深度学习

前言

这个问题从未遇见过,是一位前辈问我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);
            }

复制代码

 https://img1.sycdn.imooc.com//5b0a659800018fb409500145.jpg

通过上述测试内存基本在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赋值给静态变量,然后在我们调用的方法中直接获取容器中的上下文,这样就免去了传递的麻烦。

https://img1.sycdn.imooc.com//5b0a65a100014c4b09410143.jpg

经过我们上述改造后最终运行内存达到了比较可怕的三百多兆,看来上下文压根就没进行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);
                }
            }

复制代码

https://img1.sycdn.imooc.com//5b0a65a9000149b909460181.jpg

 

原以为是上下文没有及时得到释放而导致内存激增,但是看到上述结果依然一样没有任何改变,问题是不是到此就结束了呢?下面我们改变注入上下文的方式看看,如下:

复制代码

            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));

复制代码

https://img1.sycdn.imooc.com//5b0a65c10001dcfd09540148.jpg

当我们改变了注入上下文方式后发现此时不会造成内存泄漏,也就是说上下文得到了GC,无论是我是否是手动释放上下文即通过Using包括或者不包括都不会出现内存泄漏问题。通过注入方式不同得到的结果截然不同,但是在我们的理解中通过AddDbContext注入上下文中的第二个参数是默认为Scope,那和我们通过AddScoped注入上下文应该是一样对不对,那为何结果又不同呢?岂不是冲突了吗?在Web不会出现这样的问题,未深入研究,我猜测其原因可能如下:

通过AddDbContext注入上下文只适用于Web应用程序即只对Web应用程序有效而对控制台程序可能无效,同时在Web应用程序中AddDbContext注入上下文和AddScoped注入上下文一致,而对于控制台程序存在不一致问题。一言以蔽之,在Web和Console中通过AddDbContext注入上下文可能存在处理机制不同。

总结

不知如上浅薄分析是否有漏洞或者说代码有错误的地方,期待看到本文的您能有更深入的见解可留下您的评论,如果结果是这样不建议在控制台中使用AddDbContext方法注入上下文。

原文出处  作者:Jeffcky

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消