【问题标题】:How to return FileStreamResult with SqlDataReader如何使用 SqlDataReader 返回 FileStreamResult
【发布时间】:2021-03-11 20:10:27
【问题描述】:

我有一个 ASP.NET Core 项目,可以下载存储在 SQL Server 中的大文件。它适用于小文件,但大文件通常会在下载之前被读入内存而超时。

所以我正在努力改进它。

基于SQL Client streaming support examples,我已将代码更新为以下内容:

public async Task<FileStreamResult> DownloadFileAsync(int id)
{
     ApplicationUser user = await _userManager.GetUserAsync(HttpContext.User);
     var file = await this._attachmentRepository.GetFileAsync(id);

     using (SqlConnection connection = new SqlConnection(this.ConnectionString))
     {
         await connection.OpenAsync();

         using (SqlCommand command = new SqlCommand("SELECT [Content] FROM [Attachments] WHERE [AttachmentId] = @id", connection))
        {
             command.Parameters.AddWithValue("id", file.AttachmentId);
             SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess);

             if (await reader.ReadAsync())
             {
                 if (!(await reader.IsDBNullAsync(0)))
                 {
                     Stream stream = reader.GetStream(0);
                     var result = new FileStreamResult(stream, file.ContentType)
                     {
                         FileDownloadName = file.FileName
                     };

                     return result;
                 }
             }
         }
     }

     return null;   
 }

但是当我测试时,它会抛出这个异常:

无法访问已处置的对象。对象名称:'SqlSequentialStream'

有没有办法修复这个异常?

【问题讨论】:

  • 测试删除using,因为它可能会在请求完成之前处理流
  • 我测试过,我认为这就是问题所在。我将测试更多以确认。

标签: sql-server asp.net-core


【解决方案1】:

当您执行 return 时,您的 using 语句都会触发,从而释放您的连接和命令,但重点是让流复制在后台发生之后 你的功能完成了。

对于这种模式,您将不得不删除using 调用,并在流复制完成时触发垃圾收集。 FileStreamResult 至少应该在你给它的流上调用Dispose,这应该取消根命令和连接,以便稍后完成和关闭。

【讨论】:

    【解决方案2】:

    这是工作代码,比没有流式传输要快得多:

    [HttpGet("download")]
    public async Task<FileStreamResult> DownloadFileAsync(int id)
    {
    
        var connectionString = _configuration.GetConnectionString("DefaultConnection");
    
        ApplicationUser user = await _userManager.GetUserAsync(HttpContext.User);
    
        var fileInfo = await this._attachmentRepository.GetAttachmentInfoByIdAsync(id);
    
        SqlConnection connection = new SqlConnection(connectionString);
            
        await connection.OpenAsync();
        SqlCommand command = new SqlCommand("SELECT [Content] FROM [Attachments] WHERE [AttachmentId]=@id", connection);
        command.Parameters.AddWithValue("id", fileInfo.Id);
    
        // The reader needs to be executed with the SequentialAccess behavior to enable network streaming
        // Otherwise ReadAsync will buffer the entire BLOB into memory which can cause scalability issues or even OutOfMemoryExceptions
        SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess);
                    
        if (await reader.ReadAsync())
        {
            if (!(await reader.IsDBNullAsync(0)))
            {
                Stream stream = reader.GetStream(0);        
                var result = new FileStreamResult(stream, fileInfo.ContentType)
                {
                    FileDownloadName = fileInfo.FileName
                };
                return result;  
            }
        }
        return null;    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-03-08
      • 1970-01-01
      • 2013-10-24
      • 1970-01-01
      • 2015-10-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多