实体框架 - OutOfMemory异常

Chr*_*ris 0 c# silverlight entity-framework out-of-memory

我正在开发一个Silverlight业务应用程序,并希望实现"多部分"上传,它将单个文件拆分为大小为4096KB的部分.要将这些部分从客户端上传到服务器,我使用的是WebClient(客户端)和通用处理程序(*.ashx,服务器端).

策略:在第一部分中,创建了一个Entity Framework类的新实例.该对象具有字段/属性"binary"(在SQL中它是varbinary(MAX),在Entity Framework中它是byte []).我将第一部分存储在属性"binary"中并执行SaveChanges().然后,处理程序将此新对象的ID(主键)返回给客户端.

除了我的文件的第二部分之外,对服务器的第二个请求包含在第一个请求之后返回的ID.在服务器上,我从数据库加载以前创建的对象并附加第二部分.

myobject.binary = myobject.binary.Concat(bytes).ToArray<byte>();
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

有没有人有想法,我的实施有什么问题?如果您需要更多信息或代码段,请告诉我.

亲切的问候,克里斯

Kri*_*ten 5

想一想.上传(例如)130 MB后,执行此行需要多少内存:

myobject.binary = myobject.binary.Concat(bytes).ToArray<byte>();
Run Code Online (Sandbox Code Playgroud)

显然,前一个数组在内存中,即130 MB.不知怎的,新阵列也必须在内存中,另外130 MB,对吗?

实际上情况要糟糕得多.Concat()正在产生一个序列,并且ToArray()不知道它会有多大.

那么.ToArray()它是什么呢,它创建了一个内部缓冲区并开始用.Concat()迭代器的输出填充它.显然,它不知道缓冲区应该有多大,所以每隔一段时间它就会发现有更多的字节进入,而缓冲区可以容纳.然后需要创建一个更大的缓冲区.它将做的是创建一个比前一个缓冲区大两倍的缓冲区,复制每个缓冲区并开始使用新缓冲区.但这意味着在某些时候,旧缓冲区和新缓冲区必须同时在内存中.

在某些时候,旧缓冲区将为128 MB,新缓冲区将为256 MB.加上130 MB的旧文件,大约是半个千兆字节.现在让我们希望没有两个(或更多)用户同时这样做.

我建议你使用不同的机制.例如,将上载的chuncks存储在磁盘上的临时文件中.当一个新的chunck进来时,只需附加到该文件.只有在上传完成后,才能对文件执行任何操作,例如将其存储在数据库中.

另请注意,.NET中数组的最大大小受31位索引的限制.因此,无论系统中有多少RAM,字节数组的最大大小为2 GB.

最后:如果您正在处理这么大的内存块,请确保您在64位进程中运行,至少在.NET 4.5上运行,这样您就可以利用.NET 4.5中的大对象堆改进.但即使这并不神奇,因为"Out of Memory"并不是指物理内存.