2012-03-03 56 views
0

我有一個EF 4.2 EDMX模型,我在多租戶應用程序中使用。我連接到大約100個使用相同EDM模型的數據庫。第一次訪問每個數據庫時,我的工作集增加了〜12Mb,這看起來大部分是由EDM元數據緩存取得的。內存使用率永遠不會回落。我認爲元數據/查詢緩存可以共享,因爲它是相同的模型。使用共享模型的實體框架內存使用

尋找建議,以減少我的記憶足跡,但我懷疑我無法控制這一點。

注意:同樣的情況並不是CodeFirst的問題(我們也在使用它),但是我們有很多仍然使用EDMX模型的代碼,現在無法完全轉換它。

謝謝!

+0

你看過跟蹤與無跟蹤效果嗎?或者你總是需要跟蹤? – 2012-03-03 18:37:05

+0

我們再也不需要跟蹤,但它似乎不有所作爲無論是在與否。無論如何,當GC運行時,應該清理這種內存,我想。 – jlew 2012-03-03 18:51:38

回答

5

我相信你可以通過自己緩存MetadataWorkspace來得到你想要的。這實際上是DbContext在使用Code First時在內部執行的操作。這並不容易,但我制定了一個我認爲應該可以工作的快速原型。

這裏的基本思路是讓EF創建一個MetadataWorkspace一次,然後緩存並在每次需要創建上下文實例時明確使用它。這顯然只在每個上下文實例使用相同的模型時纔有效 - 相同的EDMX。爲了使這項工作,我創建派生的ObjectContext處理該緩存:

public class SingleModelCachingObjectContext : ObjectContext 
{ 
    private static readonly object WorkspaceLock = new object(); 
    private static MetadataWorkspace _workspace; 

    public SingleModelCachingObjectContext(string connectionStringName) 
     : base(CreateEntityConnection(connectionStringName)) 
    { 
    } 

    protected override void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      ((EntityConnection)Connection).StoreConnection.Dispose(); 
     } 
    } 

    private static EntityConnection CreateEntityConnection(string connectionStringName) 
    { 
     lock (WorkspaceLock) 
     { 
      if (_workspace == null) 
      { 
       _workspace = new EntityConnection("name=" + connectionStringName).GetMetadataWorkspace(); 
      } 
     } 

     var builder = 
      new DbConnectionStringBuilder 
      { 
       ConnectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString 
      }; 

     var storeConnection = DbProviderFactories.GetFactory((string)builder["provider"]).CreateConnection(); 
     storeConnection.ConnectionString = (string)builder["provider connection string"]; 

     return new EntityConnection(_workspace, storeConnection); 
    } 
} 

你會再通過像這樣在你的DbContext類創建一個構造函數中使用這樣的:

public MyDbContext(string connectionStringName) 
    : base(new SingleModelCachingObjectContext(connectionStringName), 
      dbContextOwnsObjectContext: true) 
{ 
} 

這是如何工作的。當您創建DbContext的實例時,它會依次創建一個SingleModelCachingObjectContext的實例,傳遞要使用的EF連接字符串的名稱。它還告訴DbContext在處理DbContext時處置這個ObjectContext。

在SingleModelCachingObjectContext中,EF連接字符串用於創建MetadataWorkspace並在創建後將其緩存在靜態字段中。這是非常簡單的緩存和簡單的線程安全與鎖 - 隨意使其更適合您的應用程序的需求。

在獲得MetadataWorkspace後,現在解析EF連接字符串以獲取商店連接字符串和提供商。然後這用於創建一個正常的商店連接。

商店連接和緩存的MetadataWorkspace用於創建一個EntityConnection,然後是一個ObjectContext,它將使用緩存的MetadataWorkspace,而不是使用正常的緩存機制。

此ObjectContext用於備份DbContext。 Dispose方法被覆蓋,所以商店連接不會泄漏。當DbContext被處置時,它將處理ObjectContext,它將依次調用Dispose並處理存儲連接。

我沒有真正測試過這個以確保它運行。知道它是否確實有助於解決內存使用問題將會非常有趣。

+0

經過測試,確實解決了這個問題。謝謝! – jlew 2012-03-04 04:02:59

0

我沒有你的問題,但我的頭頂部的一些建議許多經驗:

你可以使用ClearCache()方法?

同時嘗試使用:

protected override void OnModelCreating(DbModelBuilder modelBuilder) 
{ 
    modelBuilder.Conventions.Remove<IncludeMetadataConvention>(); 
} 

請注意,如果您刪除EdmMetadata表則會有什麼檢查,如果數據庫架構模型相匹配。

嘗試使用T4模板(如DbContext模板或Poco生成模板)預生成視圖(http://blogs.msdn.com/b/adonet/archive/2010/01/25/walkthrough-poco-template-for-在實體-framework.aspx)。

問候。

+0

這是代碼第一,不是嗎? – jlew 2012-03-03 21:14:39

+0

預生成視圖中使用DatabaseFirst,並建議使用這種方法(詳細信息:http://msdn.microsoft.com/en-us/library/bb896240.aspx)嘗試使用POCO實體發生器的http:// visualstudiogallery .msdn.microsoft.com/23df0450-5677-4926-96cc-173d02752313或者您已經更新您的EF使用的DbContext發電機。 – 2012-03-03 22:47:24

+0

我可以嘗試這些事情,但鏈接中沒有任何內容表明他們會幫助我解決記憶問題。 – jlew 2012-03-04 00:54:54

0

我加入了Arthur給出的答案。我在使用Arthur提供的解決方案時遇到了一個問題。我有兩個映射到EF模型的存儲過程,當我執行它們時,它們失敗並顯示以下消息。

System.InvalidOperationException被捕獲 Message =「EntityCommand.CommandText的值對於StoredProcedure命令無效。EntityCommand.CommandText值的格式必須是'ContainerName.FunctionImportName'。」 Source =「System.Data.Entity」

這是因爲使用MetadataWorkspace初始化上下文時未設置DefaultContainerName而發生的。爲了正確工作,我在下面進行了修改。

我採取略有不同的方式使用,而不是閱讀,因爲在多租戶數據塊,我們將是配置不sotring連接字符串,並通過它在運行時的情況下表單配置EFConnection。還使用泛型,以便您可以在不同的上下文之間共享實現。也可以改變上面的實現,以便僅當需要時才鎖定線程,即當設置工作空間時。

public class SingleModelCachingObjectContext<T> : ObjectContext 
{ 
    private static readonly object WorkspaceLock = new object(); 
    private static MetadataWorkspace _workspace; 

    public SingleModelCachingObjectContext(string connectionString) 
     : base(CreateEntityConnection(connectionString)) 
    { 
     DefaultContainerName = typeof (T).Name; 
    } 

    protected override void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      ((EntityConnection)Connection).StoreConnection.Dispose(); 
     } 
    } 

    protected static EntityConnection CreateEntityConnection(string connectionString) 
    { 
     if (_workspace == null) 
     { 
      lock (WorkspaceLock) 
      { 
       _workspace = new EntityConnection(connectionString).GetMetadataWorkspace(); 
      } 
     } 

     var builder = 
      new DbConnectionStringBuilder 
      { 
       ConnectionString = connectionString 
      }; 

     var storeConnection = DbProviderFactories.GetFactory((string)builder["provider"]).CreateConnection(); 
     storeConnection.ConnectionString = (string)builder["provider connection string"]; 
     return new EntityConnection(_workspace, storeConnection); 
    } 
}