EntityFramework Core依賴注入上下文方式不一樣形成內存泄漏了解一下?

前言

這個問題從未碰見過,是一位前輩問我EF Core內存泄漏問題時我纔去深刻探討這個問題,剛開始我比較驚訝,竟然還有這種問題,而後就有了本文,直接拿前輩的示例代碼並稍加修改爲就了此文,但願對在自學EF Core過程當中的童鞋能有些許幫助。ide

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類中的方法,以下: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

相關文章
相關標籤/搜索