Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/318.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 从SQL Server检索blob数据的最节省内存的方法_C#_Sql Server_Ado.net_Out Of Memory - Fatal编程技术网

C# 从SQL Server检索blob数据的最节省内存的方法

C# 从SQL Server检索blob数据的最节省内存的方法,c#,sql-server,ado.net,out-of-memory,C#,Sql Server,Ado.net,Out Of Memory,从SQL Server检索大型blob数据时出现内存不足异常。我正在调用一个存储过程,它返回6列简单数据和1列varbinary(max)data列 我正在使用此代码执行存储过程: m_DataReader = cmd.ExecuteReader(CommandBehavior.SequentialAccess); 我要确保按照列顺序从数据读取器中读取列 看 对于varbinary(max)列,我是这样读取数据的: DocBytes = m_DataReader.GetValue(i) as

从SQL Server检索大型blob数据时出现内存不足异常。我正在调用一个存储过程,它返回6列简单数据和1列
varbinary(max)
data列

我正在使用此代码执行存储过程:

m_DataReader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
我要确保按照列顺序从数据读取器中读取列

对于
varbinary(max)
列,我是这样读取数据的:

DocBytes = m_DataReader.GetValue(i) as byte[];
我注意到,在内存不足时,我的内存中似乎有2个字节数组副本。一个在
DocBytes
数组中,另一个在
SqlDataReader
的内部缓冲区中

为什么会有这本书的副本?我假设我将传递一个引用,或者这是因为
SqlDataReader
提供数据的内部方式,即它总是提供一个副本

是否有一种更节省内存的方法从数据库中读取数据?

我已经研究了新的.NET4.5方法,但不幸的是,我没有能力传递流-我需要内存中的字节-因此我无法遵循流到文件或web响应的其他示例。但我想尝试确保内存中一次只存在一个副本

我得出的结论是,这可能正是它必须的方式,而复制副本只是一个尚未被垃圾收集的缓冲区。我真的不想在强制垃圾收集方面浪费时间,我希望有人对其他方法有一些想法

我已经研究了新的.NET 4.5 GetStream方法,但不幸的是, 我没有能力传递数据流,我需要输入字节 记忆

所以你所要做的就是从这个流中读取一个字节数组


或者,您可以尝试使用如下所示的
GetBytes
方法从读卡器中分小块读取它:

从SQL检索二进制数据时,您可以选择。假设您使用varbinary(image是去擦写的)作为数据类型,您可以返回所有数据,也可以使用简单的子字符串函数仅返回部分数据。如果二进制文件很大(比如1GB),那么返回所有数据将占用大量内存

如果是这种情况,您可以选择采用更迭代的方法返回数据。假设它是一个1GB的二进制文件,你可以让程序在100mb的数据块中循环,将每个数据块写入磁盘,然后丢弃缓冲区,然后返回下一个100mb数据块

要获取要使用的第一个块,请执行以下操作:

Declare @ChunkCounter as integer
Declare @Data as varbinary(max)
Declare @ChunkSize as integer = 10000000
Declare @bytes as integer
Select @bytes = datalength(YourField) from YourTable where ID = YourID
If @bytes> @ChunkSize 
      Begin 
           /* use substring to get the first chunksize   */ 
           Select @data= substring(YourField,0,@ChunkSize), @Chunkcounter +1 as 'ChunkCounter'
           FROM YourTable   
           where ID = YourID
      End 
Else
      Begin ....

你知道数据的长度吗?在这种情况下,您可以使用流方法将数据复制到一个大小完美的
字节[]
。这将消除ADO.NET中在非流情况下出现的双缓冲

DocBytes=m_DataReader.GetValue(i)作为字节[]

这将创建大小数据长度(列名称)的缓冲区
然后将其完整复制到MemoryStream中
当DATA_LENGTH(column_name)是一个大值时,情况就不好了
您需要通过缓冲区将其复制到memorystream

此外,如果文件太大,请将其写入临时文件,而不是将其完整存储在MemoryStream中

我就是这样做的

    // http://stackoverflow.com/questions/2885335/clr-sql-assembly-get-the-bytestream
    // http://stackoverflow.com/questions/891617/how-to-read-a-image-by-idatareader
    // http://stackoverflow.com/questions/4103406/extracting-a-net-assembly-from-sql-server-2005
    public static void RetrieveFileStream(System.Data.IDbCommand cmd, string columnName, string path)
    {
        using (System.Data.IDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess | System.Data.CommandBehavior.CloseConnection))
        {
            bool hasRows = reader.Read();
            if (hasRows)
            {
                const int BUFFER_SIZE = 1024 * 1024 * 10; // 10 MB
                byte[] buffer = new byte[BUFFER_SIZE];

                int col = string.IsNullOrEmpty(columnName) ? 0 : reader.GetOrdinal(columnName);
                int bytesRead = 0;
                int offset = 0;

                // Write the byte stream out to disk
                using (System.IO.FileStream bytestream = new System.IO.FileStream(path, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
                {
                    while ((bytesRead = (int)reader.GetBytes(col, offset, buffer, 0, BUFFER_SIZE)) > 0)
                    {
                        bytestream.Write(buffer, 0, bytesRead);
                        offset += bytesRead;
                    } // Whend

                    bytestream.Close();
                } // End Using bytestream 

            } // End if (!hasRows)

            reader.Close();
        } // End Using reader

    } // End Function RetrieveFile
采用此代码编写memoryStream很简单
也许您需要将缓冲区大小变小或变大

    public static System.IO.MemoryStream RetrieveMemoryStream(System.Data.IDbCommand cmd, string columnName, string path)
    {
        System.IO.MemoryStream ms = new System.IO.MemoryStream();

        using (System.Data.IDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess | System.Data.CommandBehavior.CloseConnection))
        {
            bool hasRows = reader.Read();
            if (hasRows)
            {
                const int BUFFER_SIZE = 1024 * 1024 * 10; // 10 MB
                byte[] buffer = new byte[BUFFER_SIZE];

                int col = string.IsNullOrEmpty(columnName) ? 0 : reader.GetOrdinal(columnName);
                int bytesRead = 0;
                int offset = 0;

                // Write the byte stream out to disk
                while ((bytesRead = (int)reader.GetBytes(col, offset, buffer, 0, BUFFER_SIZE)) > 0)
                {
                    ms.Write(buffer, 0, bytesRead);
                    offset += bytesRead;
                } // Whend

            } // End if (!hasRows)

            reader.Close();
        } // End Using reader

        return ms;
    } // End Function RetrieveFile

如果你需要把它放到Acth.UutPoSt流中,考虑直接写在那里,而不是通过MythyStudio。toRayay.(+)+写字节。

< P>问题是,<代码> dDATaReaDe.GETStand()/Cyto>创建了<代码>内存流,并用该字段的数据填充该流。 为了避免这种情况,我创建了一个扩展方法:

public static class DataReaderExtensions
{
    /// <summary>
    /// writes the content of the field into a stream
    /// </summary>
    /// <param name="reader"></param>
    /// <param name="ordinal"></param>
    /// <param name="stream"></param>
    /// <returns>number of written bytes</returns>
    public static long WriteToStream(this IDataReader reader, int ordinal, Stream stream)
    {
        if (stream == null)
            throw new ArgumentNullException("stream");

        if (reader.IsDBNull(ordinal))
            return 0;

        long num = 0L;
        byte[] array = new byte[8192];
        long bytes;
        do
        {
            bytes = reader.GetBytes(ordinal, num, array, 0, array.Length);
            stream.Write(array, 0, (int)bytes);
            num += bytes;
        }
        while (bytes > 0L);
        return num;
    }

    /// <summary>
    /// writes the content of the field into a stream
    /// </summary>
    /// <param name="reader"></param>
    /// <param name="field"></param>
    /// <param name="stream"></param>
    /// <returns>number of written bytes</returns>
    public static long WriteToStream(this IDataReader reader, string field, Stream stream)
    {
        int ordinal = reader.GetOrdinal(field);
        return WriteToStream(reader, ordinal, stream);
    }
}
公共静态类DataReaderExtensions
{
/// 
///将字段的内容写入流
/// 
/// 
/// 
/// 
///写入字节数
公共静态long-WriteToStream(此IDataReader读取器,int-ordinal,Stream-Stream)
{
if(流==null)
抛出新的ArgumentNullException(“流”);
if(读卡器IsDBNull(序号))
返回0;
长数值=0L;
字节[]数组=新字节[8192];
长字节;
做
{
字节=reader.GetBytes(序号,num,数组,0,数组.Length);
stream.Write(数组,0,(int)字节);
num+=字节;
}
而(字节>0L);
返回num;
}
/// 
///将字段的内容写入流
/// 
/// 
/// 
/// 
///写入字节数
公共静态长写流(此IDataReader读取器、字符串字段、流)
{
int ordinal=reader.GetOrdinal(字段);
返回WriteToStream(读取器、序号、流);
}
}

但是为什么要麻烦地将
.GetValue(i)作为字节[]
更改为
GetStream
方法呢?它的内存效率更高吗?流只是一个指针。只有当您开始从中读取字节时,才会从套接字中读取字节。但是我不打算使用
GetStream
重新实现以获取完整的字节,因为
。GetValue(I)as byte[]
已经为我做了这件事,除非它更具内存效率。知道您可以将varbinary作为子字符串非常有用!我想这将降低
SqlDataReader
的每个实例的内存需求,从而有助于垃圾收集。但是db往返次数增加了。更多的往返次数,或者一次大的成功,这就是折衷。根据我们的经验,如果以较小的数据块获取数据,则很容易管理内存情况。如果有10个人点击服务器,要求每人提供1gb的文件,那就足够让服务器停机了。但是10个人在服务器上点击100次以获得较小的数据块会更好地分散负载。嗯