2013-10-16 186 views
0

我正在開發Silverlight業務應用程序並希望實施「多部分」上載,它將單個文件拆分爲大小爲4096KB的部分。要將這些部分從客戶端上傳到服務器,我使用WebClient(客戶端)和通用處理程序(* .ashx,服務器端)。實體框架 - OutOfMemory異常

策略: 第一部分創建了實體框架類的新實例。這個對象有一個字段/屬性「二進制」(在SQL中它是一個varbinary(MAX),在實體框架中它是一個字節[])。我將第一部分存儲在屬性「binary」中並執行SaveChanges()。然後,處理程序將這個新對象的ID(主鍵)返回給客戶端。

除了我的文件的第二部分之外,對服務器的第二個請求包含第一個請求後返回的ID。在服務器上,我從數據庫加載先前創建的對象並追加第二部分。

myobject.binary = myobject.binary.Concat(bytes).ToArray<byte>(); 

MyObject來是先前創建的對象,字節我要追加到二進制特性的部件。

我重複這個「策略」,直到整個文件上傳到服務器。這適用於最大大小爲〜78MB的文件。對於大小爲〜83MB的文件,它是零星的。大小爲〜140MB的文件將在SaveChanges()處以 OutOfMemory異常中止。

堆棧跟蹤

at System.Object.MemberwiseClone() 
at System.Array.Clone() 
at System.Data.Common.CommandTrees.DbConstantExpression..ctor(TypeUsage resultType, Object value) 
at System.Data.Mapping.Update.Internal.UpdateCompiler.GenerateValueExpression(EdmProperty property, PropagatorResult value) 
at System.Data.Mapping.Update.Internal.UpdateCompiler.BuildSetClauses(DbExpressionBinding target, PropagatorResult row, PropagatorResult originalRow, TableChangeProcessor processor, Boolean insertMode, Dictionary`2& outputIdentifiers, DbExpression& returning, Boolean& rowMustBeTouched) 
at System.Data.Mapping.Update.Internal.UpdateCompiler.BuildUpdateCommand(PropagatorResult oldRow, PropagatorResult newRow, TableChangeProcessor processor) 
at System.Data.Mapping.Update.Internal.TableChangeProcessor.CompileCommands(ChangeNode changeNode, UpdateCompiler compiler) 
at System.Data.Mapping.Update.Internal.UpdateTranslator.<ProduceDynamicCommands>d__0.MoveNext() 
at System.Linq.Enumerable.<ConcatIterator>d__71`1.MoveNext() 
at System.Data.Mapping.Update.Internal.UpdateCommandOrderer..ctor(IEnumerable`1 commands, UpdateTranslator translator) 
at System.Data.Mapping.Update.Internal.UpdateTranslator.ProduceCommands() 
at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter) 
at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache) 
at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options) 
at MyObjectContext.SaveChanges(SaveOptions options) in PathToMyEntityModel.cs:Line 83. 
at System.Data.Objects.ObjectContext.SaveChanges() 
at MultipartUpload.ProcessRequest(HttpContext context) in PathToGenericHandler.ashx.cs:Line 73. 

有沒有人有一個想法,有什麼錯我的執行?如果您需要更多信息或代碼片段,請讓我知道它。

親切的問候, 克里斯

+0

恐怕只是增加機器的內存將有所幫助。 'MemberwiseClone'只需要很多內存。在編寫更新語句之前,將始終克隆二進制值。 –

+0

感謝您的回答。我不認爲,這是真正的問題。我的本地機器(我運行和調試我的應用程序)有12GB內存。在您發表評論後,我檢查了我的任務管理器......幾乎沒有內存空閒。所以我重新啓動了我的機器並重新開始調試(這次的可用內存超過7GB),並且它再次導致OutOfMemoryException。 :-( – Chris

+0

還有更多這樣的內容:http://stackoverflow.com/q/14186256/861716。因此,即使提高內存量也不會總是有所幫助。 –

回答

5

想想吧。具有上傳(例如)130 MB,多少內存來執行該線所需後:

myobject.binary = myobject.binary.Concat(bytes).ToArray<byte>(); 

顯然,前面的陣列是在存儲器中,這是130 MB。不知何故,新陣列也必須在內存中,另一個130 MB,對不對?

實際上情況更糟。 Concat()正在產生一個序列,並且ToArray()不知道它會有多大。

所以.ToArray()做的是它創建一個內部緩衝區,並開始使用迭代器的輸出填充它。顯然,它不知道緩衝區應該有多大,所以每隔一段時間它就會發現有更多的字節進來,而不是緩衝區可以容納的。然後它需要創建一個更大的緩衝區。它會做的是創建一個比前一個大兩倍的緩衝區,每隔一段時間複製並開始使用新的緩衝區。但這意味着在某個時候,舊緩衝區和新緩衝區必須同時在內存中。

在某些時候,舊的緩衝區將是128 MB,新的緩衝區將是256 MB。與130 MB的舊文件一起,大約是半個千兆字節。現在讓我們希望沒有兩個(或更多)用戶同時做到這一點。

我建議你使用不同的機制。例如,將上傳的chunck存儲在磁盤上的臨時文件中。當新的chunck進來時,只需附加到文件。只有上傳完成後,您才能對文件進行任何操作,例如將其存儲在數據庫中。

另請注意,.NET中數組的最大大小受31位索引的限制。因此,無論系統中有多少RAM,字節數組的最大大小都是2 GB。

最後:如果你正在處理這麼大的內存塊,確保你在64位進程中運行,並且至少在.NET 4.5上運行,所以你可以利用Large Object Heap Improvements in .NET 4.5。但即使這不是神奇的「Out Of Memory」 Does Not Refer to Physical Memory