实体框架 - 内存不足异常
本文关键字:异常 内存不足 框架 实体 | 更新日期: 2023-09-27 18:35:39
>我正在开发一个 Silverlight Business 应用程序,并希望实现"多部分"上传,它将单个文件拆分为大小为 4096KB 的部分。为了将这些部分从客户端上传到服务器,我使用的是WebClient(客户端)和通用处理程序(*.ashx,服务器端)。
策略:使用第一部分,将创建实体框架类的新实例。此对象具有字段/属性"二进制"(在SQL中它是varbinary(MAX),在实体框架中是byte[])。我将第一部分存储在属性"二进制"中并执行 SaveChanges()。然后,处理程序将此新对象的 ID(主键)返回给客户端。
对服务器的第二个请求在我的文件的第二部分旁边包含第一个请求后返回的 ID。在服务器上,我从数据库中加载先前创建的对象并附加第二部分。
myobject.binary = myobject.binary.Concat(bytes).ToArray<byte>();
myobject 是之前创建的对象,字节是我想附加到二进制属性的部分。
我重复这个"策略",直到整个文件上传到服务器。这适用于最大大小为 ~78MB 的文件。对于大小为 ~83MB 的文件,它是零星工作的。大小为 ~140MB 的文件将在 SaveChanges() 时中止并出现内存不足异常。
堆栈跟踪
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.
有没有人有想法,我的实现有什么问题?如果您需要更多信息或代码片段,请告诉我。
亲切问候克里斯
想一想。上传(例如)130 MB后,执行此行需要多少内存:
myobject.binary = myobject.binary.Concat(bytes).ToArray<byte>();
显然,前面的数组在内存中,即 130 MB。不知何故,新阵列也必须在内存中,另外还有 130 MB,对吧?
实际上情况要糟糕得多。 Concat()
正在制作一个序列,ToArray()
不知道它会有多大。
因此,.ToArray()
所做的是创建一个内部缓冲区,并开始用.Concat()
迭代器的输出填充它。显然,它不知道缓冲区应该有多大,所以每隔一段时间它就会发现进来的字节数超过了缓冲区所能容纳的字节数。然后,它需要创建一个更大的缓冲区。它将要做的是创建一个比前一个缓冲区大两倍的缓冲区,复制每个缓冲区并开始使用新的缓冲区。但这意味着在某些时候,旧缓冲区和新缓冲区必须同时在内存中。
在某些时候,旧缓冲区将为 128 MB,新缓冲区将为 256 MB。加上130 MB的旧文件,大约是半千兆字节。现在,我们希望没有两个(或更多)用户同时执行此操作。
我建议你使用不同的机制。例如,将上传的 chucks 存储在磁盘上的临时文件中。当有新的 chuck 进来时,只需附加到文件中即可。只有在上传完成后,才对文件执行任何操作,例如将其存储在数据库中。
另请注意,.NET 中数组的最大大小受 31 位索引的限制。因此,字节数组的最大大小为 2 GB,无论您系统中有多少 RAM。
最后:如果要处理这么大的内存块,请确保在 64 位进程中运行,并且至少在 .NET 4.5 上运行,以便可以利用 .NET 4.5 中的大型对象堆改进。但即使这样也不是魔法,因为"内存不足"并不是指物理记忆。