当Entity Framework查询过大的Varbinary类型数据时,会出现System.OutOfMemoryException错误。

3

我正在尝试查询一个包含文件(1.2 Gb)的varbinary列。

我正在使用Entity Framework。请参见下面:

要测试的数据库

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]

测试数据(任何1GB大小的文件)

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;
        }
    }

我可以在 SQL Server 上使用 Select * From BigData 正常查询。

但是,在 Entity Framework(或者 ADO 命令)中我会遇到以下异常:

System.OutOfMemoryException

有人知道如何解决这个问题吗?


1
获取更多的[虚拟]内存或更改代码以查询更少的内存。这几乎就是您所期望的。 - Servy
1
"...修改代码以查询更少的数据" -> 我考虑过这个问题,但我不知道如何从一个1Gb的字段中查询更少的数据。 关于内存,服务器有很多内存,我认为问题是在.Net上的限制,但我不知道如何增加限制。 - user3812703
3个回答

2
哇,那是很多数据。我真的认为你不应该使用 EF 来获取这些数据,而应该使用经典的 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
}

个人而言,我通常会尝试复制相关代码,因为链接可能会突然失效,这样我们就会丢失信息。 - Pseudonym
1
我修改了这段代码,它运行良好。我不再遇到异常了。我使用FileStream.WriteByte来连接缓冲区。对于文本文件,它运行良好,但是当我尝试使用压缩文件时,文件会损坏。 公司的文件是压缩后的文本文件,大小在5GB至7GB之间,解压后大小为500MB或更大。我正在进行更多测试以查看是否可以解决问题。如果我解决了,我会在这里发布。 - user3812703

2

尝试使用EF的"AsNoTracking()"选项加载数据!

示例: MyContext.MyTable.AsNoTracking().Where( x => x.....)


0

看起来实体框架不支持将数据流式传输到 varbinary 字段。

您有几个选项。

  • 对于大型传输,请使用 ADO.NET。
  • 将数据库切换为 FileStream 而不是 varbinary。

编辑: 假设您正在使用 .NET 4.5,则应使用 SqlDataReader.GetStream。这将允许在不必将整个文件加载到内存中的情况下流式传输文件。


1
我可以用Entity Framework类型Varbinary从500Mb的数据中查询数据。我无法更改数据类型,因为这是一个拥有数百万个文件的公司数据库。我尝试过ADO并得到了相同的异常。 - user3812703
1
编辑:假设您正在使用.NET 4.5,则应使用SqlDataReader.GetStream。这将允许流式传输文件,而无需将整个文件加载到内存中。您的答案很好,但我检查了该项目,不幸的是它是用.4.0 Net创建的。:(是否有类似于Net 4.0的东西? - user3812703
看起来 DataReader.Getbytes 可以在早期版本中使用。但是使用起来需要多做一些工作。 - David Crowell
@Elizeu "我可以查询500mb的数据",这可能意味着你有大约4GB的内存。将数据加载到byte[]中是非常低效的。你可能需要几个G的内存来加载和存储500mb的数据。实际上,任何超过70kB的数据都是一个巨大的问题。 - Aron
在SqlServer 2008和.NET 3.5.1中添加了FileStream访问功能。http://msdn.microsoft.com/en-us/library/cc716724(v=vs.100).aspx - Aron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接