System.OutOfMemoryException - 当实体框架查询 Varbinary 类型的太大数据时
Posted
技术标签:
【中文标题】System.OutOfMemoryException - 当实体框架查询 Varbinary 类型的太大数据时【英文标题】:System.OutOfMemoryException - when Entity Framework is querying a too big data of Varbinary type 【发布时间】:2014-08-28 02:08:53 【问题描述】:我正在尝试查询包含文件 (1,2 Gb) 的 varbinary
列。
我正在使用实体框架。见下文:
要测试的数据库
CREATE TABLE [dbo].[BIGDATA]
(
[id] [bigint] IDENTITY(1,1) NOT NULL,
[BIGDATA] [varbinary](max) NULL,
CONSTRAINT [PK_BIGDATA] PRIMARY KEY CLUSTERED ([id] ASC)
) ON [PRIMARY]
要测试的数据(任何 1 Gb 的文件)
INSERT INTO [dbo].[BIGDATA]([BIGDATA])
VALUES
((SELECT BulkColumn FROM OPENROWSET(BULK N'C:\BigTest.txt', SINGLE_BLOB) AS Document))
控制器下载文件
public FileResult Download()
try
var context = new Models.ELOGTESTEEntities();
var idArquivo = Convert.ToInt32(1);
// The problem is here, when trying send command to SQL Server to read register
var arquivo = (from item in context.BIGDATA
where item.id.Equals(idArquivo)
select item).Single();
var mimeType = ".txt";
byte[] bytes = System.Text.Encoding.GetEncoding("iso-8859-8").GetBytes("BigTest.txt");
return File(arquivo.BIGDATA1, mimeType, System.Text.Encoding.UTF8.GetString(bytes));
catch (Exception ex)
throw ex;
我可以使用Select * From BigData
在 SQL Server 上正常查询。
但是,在实体框架(或带有 ADO 的命令)中,我得到了这个异常:
System.OutOfMemoryException
有人知道如何解决这个问题吗?
【问题讨论】:
获取更多[虚拟]内存或更改代码以减少查询。几乎完全符合您的预期。 "...更改代码以减少查询" -> 我想过,但我不知道如何从 1Gb 的字段中查询更少的数据。关于内存,服务器有很多内存,我认为问题是.Net上的限制,但我不知道如何增加限制。 【参考方案1】:哇,这是很多数据。我真的认为你不需要使用 EF 来获取这些数据,而是使用好的 'ol SqlDataReader。
鉴于您的 .net 4.0 限制,我找到了一个自定义实现,可以从大量 varbinary 列流式读取。除了查看代码并确保其中没有 .net 4.5 快捷方式之外,我无法为此获得任何功劳:
http://www.syntaxwarriors.com/2013/stream-varbinary-data-to-and-from-mssql-using-csharp/
Mods - 让我知道是否应该将此类内容复制/粘贴到答案中,因为原始 URL 可能不会持久。
编辑: 以下是链接中的代码,以防 URL 消失:
用法:
// reading and returning data to the client
VarbinaryStream filestream = new VarbinaryStream(
DbContext.Database.Connection.ConnectionString,
"FileContents",
"Content",
"ID",
(int)filepost.ID,
true);
// Do what you want with the stream here.
代码:
public class VarbinaryStream : Stream
private SqlConnection _Connection;
private string _TableName;
private string _BinaryColumn;
private string _KeyColumn;
private int _KeyValue;
private long _Offset;
private SqlDataReader _SQLReader;
private long _SQLReadPosition;
private bool _AllowedToRead = false;
public VarbinaryStream(
string ConnectionString,
string TableName,
string BinaryColumn,
string KeyColumn,
int KeyValue,
bool AllowRead = false)
// create own connection with the connection string.
_Connection = new SqlConnection(ConnectionString);
_TableName = TableName;
_BinaryColumn = BinaryColumn;
_KeyColumn = KeyColumn;
_KeyValue = KeyValue;
// only query the database for a result if we are going to be reading, otherwise skip.
_AllowedToRead = AllowRead;
if (_AllowedToRead == true)
try
if (_Connection.State != ConnectionState.Open)
_Connection.Open();
SqlCommand cmd = new SqlCommand(
@"SELECT TOP 1 [" + _BinaryColumn + @"]
FROM [dbo].[" + _TableName + @"]
WHERE [" + _KeyColumn + "] = @id",
_Connection);
cmd.Parameters.Add(new SqlParameter("@id", _KeyValue));
_SQLReader = cmd.ExecuteReader(
CommandBehavior.SequentialAccess |
CommandBehavior.SingleResult |
CommandBehavior.SingleRow |
CommandBehavior.CloseConnection);
_SQLReader.Read();
catch (Exception e)
// log errors here
// this method will be called as part of the Stream ímplementation when we try to write to our VarbinaryStream class.
public override void Write(byte[] buffer, int index, int count)
try
if (_Connection.State != ConnectionState.Open)
_Connection.Open();
if (_Offset == 0)
// for the first write we just send the bytes to the Column
SqlCommand cmd = new SqlCommand(
@"UPDATE [dbo].[" + _TableName + @"]
SET [" + _BinaryColumn + @"] = @firstchunk
WHERE [" + _KeyColumn + "] = @id",
_Connection);
cmd.Parameters.Add(new SqlParameter("@firstchunk", buffer));
cmd.Parameters.Add(new SqlParameter("@id", _KeyValue));
cmd.ExecuteNonQuery();
_Offset = count;
else
// for all updates after the first one we use the TSQL command .WRITE() to append the data in the database
SqlCommand cmd = new SqlCommand(
@"UPDATE [dbo].[" + _TableName + @"]
SET [" + _BinaryColumn + @"].WRITE(@chunk, NULL, @length)
WHERE [" + _KeyColumn + "] = @id",
_Connection);
cmd.Parameters.Add(new SqlParameter("@chunk", buffer));
cmd.Parameters.Add(new SqlParameter("@length", count));
cmd.Parameters.Add(new SqlParameter("@id", _KeyValue));
cmd.ExecuteNonQuery();
_Offset += count;
catch (Exception e)
// log errors here
// this method will be called as part of the Stream ímplementation when we try to read from our VarbinaryStream class.
public override int Read(byte[] buffer, int offset, int count)
try
long bytesRead = _SQLReader.GetBytes(0, _SQLReadPosition, buffer, offset, count);
_SQLReadPosition += bytesRead;
return (int)bytesRead;
catch (Exception e)
// log errors here
return -1;
public override bool CanRead
get return _AllowedToRead;
#region unimplemented methods
public override bool CanSeek
get return false;
public override bool CanWrite
get return true;
public override void Flush()
throw new NotImplementedException();
public override long Length
get throw new NotImplementedException();
public override long Position
get
throw new NotImplementedException();
set
throw new NotImplementedException();
public override long Seek(long offset, SeekOrigin origin)
throw new NotImplementedException();
public override void SetLength(long value)
throw new NotImplementedException();
#endregion unimplemented methods
【讨论】:
就我个人而言,我通常会尝试复制相关代码,因为链接可能会在没有警告的情况下断开,然后我们会丢失信息。 我修改了这段代码,它运行良好。我不再有例外。我正在使用 FileStream.WriteByte 加入缓冲区。它适用于文本文件,但是当我尝试使用压缩文件时,文件已损坏。公司文件是“5Gb ~ 7GB”压缩的文本文件,会产生 500mb 或更多的文件。我正在做更多的测试,看看我是否能解决。当我解决它时,我在这里发布。【参考方案2】:尝试使用 EF "AsNoTracking()" 选项加载数据!
示例: MyContext.MyTable.AsNoTracking().Where(x => x.....)
【讨论】:
【参考方案3】:Entity Framework 似乎不支持将数据流式传输到 varbinary 字段。
您有几个选择。
将大数据转移到 ADO.NET。 将您的数据库切换到 FileStream 而不是 varbinary编辑:
假设您使用的是 .NET 4.5,您应该使用 SqlDataReader.GetStream
。这将允许流式传输文件,而无需将整个内容加载到内存中。
【讨论】:
我可以使用实体框架类型 Varbinary 查询 500Mb 以下的数据。我无法更改数据类型,因为它是拥有数百万个文件的公司的数据库。我试过 ADO 也遇到了同样的异常。 “编辑:假设您使用的是 .NET 4.5,您应该使用 SqlDataReader.GetStream。这将允许流式传输文件,而无需将整个内容加载到内存中。” 你的回答很好,但我检查了这个项目,不幸的是它是用它创建的。 4.0 网。 :( ** - **Net 4.0 有类似的东西吗? 看起来DataReader.Getbytes
可用于早期版本。使用起来需要做更多的工作。
@Elizeu “我可以查询 500mb 的数据”,这可能意味着您有大约 4gb 的内存。将数据加载到 byte[] 是非常低效的。您可能需要一些 ram 才能加载和存储 500mb。事实上,任何超过 70kB 的东西都是一个大问题。
在 SqlServer 2008 和 .net 3.5.1 msdn.microsoft.com/en-us/library/cc716724(v=vs.100).aspx 中添加了 FileStream 访问权限以上是关于System.OutOfMemoryException - 当实体框架查询 Varbinary 类型的太大数据时的主要内容,如果未能解决你的问题,请参考以下文章