EF4 內存/效能改善一案


本文所要分享的內容在特定的背景下,請予以注意。

補充:有朋友回復不明本文在分享什么,這里給予補充說明
大致的情況是這樣的,有數百個相同架構的 DB 分配給不同的客戶使用。然而他們共享一套高層的邏輯組件,這些組件需要在某些情況下操作所有的這些 DB (如提取某些資料后發送郵件等)。這樣導致了鏈接發生變化,實例化 ObjectContext 並及時的釋放分配的資源,但 釋放分配資源 這個動作並沒有達到預期的效果,內存一直被吃着,效率一直下降着。文末的代碼有例子。為了解決這個問題,搜索了不少網上的資料(都是在提醒 using 和 Detach 或是 ToList),但這不是合適的解決方案。在經過一番排查后發現 EF6 不存在這個問題,效率很高,內存也不會持續增長,查看代碼得知 EF4 隨着鏈接的不同會實例化多個 MetadataWorkspace 而不去思考 Metadata 的相同性(EF6 不會)。最后文末給出了一個 BuildFromCache 的方法來解決這個問題。

背景:使用 EF4 框架,操縱多個擁有同樣架構的 DB

問題:發現隨着 DB 數量的增加其內存幾乎耗盡,查詢效率極其低下。

過程:

  1. 接到這個問題開始便大量 Google 關鍵詞: EF Memory Leaks | EF Dispose 等,發現有不少類似的問題但都木有一個可行的答案,也沒有解決這個問題。而后決定使用工具分析。
  2. 使用  ANTS Memory Profiler 7(福利進行內存分析,知道了哪些對象在吃着內存。
  3. Google 了一番那些吃內存對象的關鍵詞,也有相當多的篇幅在咨詢類似的問題,如: MetadataWorkspace | EntityConnection等。
  4. 嘗試使用了一下 EF6,這個問題居然不存在了-_- !,尼瑪看代碼吧。
  5. EntityConnection中提供的 GetMetadataWorkspace 映入眼簾,就是它了,因為 EF4 它愛上了 new

解法:EntityConnection 實例化的時候始終共享工作區(下面的范例代碼中的 BuildFromCache(string catalog) 方法)。

重現:

  • 制造背景:
View Code
DECLARE @num INT 
DECLARE @max INT 
DECLARE @catalog NVARCHAR(16)
DECLARE @dbFullName NVARCHAR(200)
DECLARE @logFullName NVARCHAR(200)
SET @num = 0
SET @max = 1 
WHILE @num < @max 
    BEGIN
        SET @catalog = 'DB' + REPLACE(STR(@num, 3), SPACE(1), '0')
        
        SET @dbFullName = N'D:\Database\' + @catalog + N'.mdf'
        SET @logFullName = N'D:\Database\' + @catalog + N'.LDF'
        
        RESTORE DATABASE @catalog
        FROM  DISK = N'D:\Database\Backup\AdventureWorks2008R2.bak' 
        WITH  FILE = 1,  
        MOVE N'AdventureWorks2008R2_Data' TO @dbFullName,  
        MOVE N'AdventureWorks2008R2_Log' TO @logFullName,  
        NOUNLOAD,  
        STATS = 10
        
        SET @num = @num + 1 
    END
  • 測試代碼:
View Code
using System;
using System.Collections.Generic;
using System.Data.EntityClient;
using System.Data.Metadata.Edm;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Globalization;
using System.Linq;

namespace Foo
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Stopwatch watch = Stopwatch.StartNew();
            IEnumerable<string> catalogs = GenerateCatalogs();
            foreach (string catalog in catalogs)
            {
                //using (AdventureWorksModelContainer container = ContainerBuilder.Build(catalog))
                using (AdventureWorksModelContainer container = ContainerBuilder.BuildFromCache(catalog))
                {
                    Employee employee = container.Employee.First(o => true);
                }
            }
            watch.Stop();
            Console.WriteLine(watch.Elapsed);
            Console.ReadKey();
        }

        private static IEnumerable<string> GenerateCatalogs()
        {
            var names = new string[20];
            for (int i = 1; i <= names.Length; i++)
                yield return string.Concat("DB", i.ToString(CultureInfo.InvariantCulture).PadLeft(3, '0'));
        }
    }

    internal class ContainerBuilder
    {
        private const string Metadata = "res://*/AdventureWorksModel.csdl|res://*/AdventureWorksModel.ssdl|res://*/AdventureWorksModel.msl";
        private const string Connection = "data source=.;initial catalog={0};integrated security=True;MultipleActiveResultSets=True";

        private static MetadataWorkspace _metadataWorkspace;

        public static AdventureWorksModelContainer Build(string catalog)
        {
            string esc = new EntityConnectionStringBuilder
                {
                    Provider = "System.Data.SqlClient",
                    ProviderConnectionString = string.Format(Connection, catalog),
                    Metadata = Metadata
                }.ToString();
            return new AdventureWorksModelContainer(esc);
        }

        public static AdventureWorksModelContainer BuildFromCache(string catalog)
        {
            string connection = string.Format(Connection, catalog);
            if (_metadataWorkspace == null)
            {
                string esc = new EntityConnectionStringBuilder
                    {
                        Provider = "System.Data.SqlClient",
                        ProviderConnectionString = connection,
                        Metadata = Metadata
                    }.ToString();
                using (var ec = new EntityConnection(esc))
                {
                    _metadataWorkspace = ec.GetMetadataWorkspace();
                }
            }
            return new AdventureWorksModelContainer(new EntityConnection(_metadataWorkspace, new SqlConnection(connection)));
        }
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM