Vb.net PushStreamContent使用SqlDataReader.ExecuteReaderAsync将CSV或分隔文件流式传输到客户端

Vb.net PushStreamContent使用SqlDataReader.ExecuteReaderAsync将CSV或分隔文件流式传输到客户端,vb.net,asynchronous,asp.net-web-api,async-await,sqldatareader,Vb.net,Asynchronous,Asp.net Web Api,Async Await,Sqldatareader,我试图利用WebAPI将大量数据从SQL Server流式传输到一个分隔文件,而不必等待整个结果集从数据库返回。我对Async和Wait的使用非常糟糕,我还没有完全理解它。似乎我想使用PushStreamContent和SqlDataReader异步方法来实现。 这是我的DataAccessLayer: Public Async Function ExecuteToResponseStream(command As SqlCommand, responseStream As IO.Stre

我试图利用WebAPI将大量数据从SQL Server流式传输到一个分隔文件,而不必等待整个结果集从数据库返回。我对Async和Wait的使用非常糟糕,我还没有完全理解它。似乎我想使用PushStreamContent和SqlDataReader异步方法来实现。 这是我的DataAccessLayer:

   Public Async Function ExecuteToResponseStream(command As SqlCommand, responseStream As IO.Stream) As Threading.Tasks.Task
      Using responseStream
         Dim newConString As String = _conString
         Using con As SqlConnection = New SqlConnection(newConString)
            Await con.OpenAsync()
            'Add params
            command.Connection = con

            Using ms As IO.MemoryStream = New IO.MemoryStream
               Using ts As IO.StreamWriter = New IO.StreamWriter(ms)

                  Using reader As SqlDataReader = Await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess)
                     While Await reader.ReadAsync()
                        Dim loValues As New List(Of String)
                        For i = 0 To reader.FieldCount - 1
                           loValues.Add(reader(i).ToString)
                        Next
                        Await ts.WriteAsync(String.Join(vbTab, loValues.ToArray))
                        Await ms.CopyToAsync(responseStream)
                     End While
                  End Using
               End Using
            End Using
         End Using
      End Using
   End Function
我的存储库有一个签名为的方法: 函数ExportAsyncid作为字符串,responseStream作为IO.Stream作为任务

最后,WebAPI客户端:

   Public Function Export(id As String) As HttpResponseMessage
      Dim resp As New HttpResponseMessage(HttpStatusCode.OK)
      Dim onStreamAvailable As Action(Of IO.Stream, HttpContent, TransportContext) = Async Sub(responseStream, content, context)
        Await repository.ExportAsync(rc, responseStream)
     End Sub

      resp.Content = New PushStreamContent(onStreamAvailable)
      resp.Content.Headers.ContentType = New Headers.MediaTypeHeaderValue("application/octet-stream")
      resp.Content.Headers.ContentDisposition = New Headers.ContentDispositionHeaderValue("attachment")
      resp.Content.Headers.ContentDisposition.FileName = "Somefilename"

      Return resp
   End Function
客户端函数似乎会适当退出并最终返回响应。对浏览器的最终响应确实看起来像一个附件,它是空的,我很喜欢流,但它似乎不是以那种神奇的异步方式发生的


此外,在我看来,这将是一个有点简单的练习,尽管我在Web上找不到一个好的实现。也许我完全错了

我发现了一些过去有用/不可用的东西

更新

存储库方法

Public Async Function ExportToCSV(inputParameters, stream As IO.Stream) As Threading.Tasks.Task
   'Build SqlCommand from inputParameters
   Await sqlClient.ReaderToDelimitedAsync(","c, _SqlCommand, stream)
End Function
自定义SqlClient包装方法

   Async Function ReaderToDelimitedAsync(delimiter As Char, command As SqlCommand, stream As IO.Stream) As Threading.Tasks.Task
  Try

     Await command.Connection.OpenAsync
     Using rdr As SqlDataReader = Await command.ExecuteReaderAsync
        Using sw As New IO.StreamWriter(stream, Text.Encoding.GetEncoding("Windows-1252"))
           Dim los As New List(Of String)
           For i = 0 To rdr.FieldCount - 1
              los.Add(String.Format("{0}{1}{0}", ControlChars.Quote, rdr.GetName(i)))
           Next
           Await sw.WriteLineAsync(String.Join(delimiter, los.ToArray))
           Await sw.FlushAsync

           While Await rdr.ReadAsync
              Dim loVals As New List(Of String)
              For i = 0 To rdr.FieldCount - 1
                 Dim strVal As String = rdr.GetValue(i).ToString
                 strVal = strVal.Replace(ControlChars.Quote, Chr(39)).Trim(Chr(10), Chr(13), Chr(32)) '.Replace(Chr(13), " ").Replace(Chr(10), " ")
                 strVal = strVal.Replace(vbCrLf + vbCrLf, vbCrLf).Replace(vbCrLf + vbCrLf, vbCrLf)
                 strVal = strVal.Substring(0, Math.Min(strVal.Length, 32000))
                 loVals.Add(String.Format("{0}{1}{0}", ControlChars.Quote, strVal))
              Next
              Await sw.WriteLineAsync(String.Join(","c, loVals.ToArray))
              Await sw.FlushAsync()
           End While

        End Using
     End Using
  Catch ex As Exception
     Throw ex
  End Try
   End Function
使用它:

  <HttpPost, ActionName("Download"), ValidateAntiForgeryHeader(False)> Public Function DownloadReport(postparameters) As HttpResponseMessage

      Dim resp As New HttpResponseMessage(HttpStatusCode.OK)

         resp.Content = New PushStreamContent(Function(stream, http_content, transportcontextcontext)
                                                 Return repository.Export(postparameters, stream)
                                              End Function, "text/csv")
         resp.Content.Headers.ContentType = New Headers.MediaTypeHeaderValue("text/csv")
     resp.Content.Headers.ContentDisposition = New Headers.ContentDispositionHeaderValue("attachment")
      resp.Content.Headers.ContentDisposition.FileName = reporttemplate.Filename

      Return resp

   End Function
我也试过这样做:

   <HttpPost, ActionName("Download"), ValidateAntiForgeryHeader(False)> Public Function DownloadReport(postparameters) As HttpResponseMessage

      Dim resp As New HttpResponseMessage(HttpStatusCode.OK)

         resp.Content = New PushStreamContent(Async Function(stream, http_content, transportcontextcontext)
                                                 Await repository.Export(postparameters, stream)
                                              End Function, "text/csv")
         resp.Content.Headers.ContentType = New Headers.MediaTypeHeaderValue("text/csv")
     resp.Content.Headers.ContentDisposition = New Headers.ContentDispositionHeaderValue("attachment")
      resp.Content.Headers.ContentDisposition.FileName = reporttemplate.Filename

      Return resp

   End Function
问题在于,直到SqlDataReader关闭EndUsing语句,下载才会在客户端启动。我的理解是,PushStreamContent应该在遇到第一次等待时返回,随后对FlushAsync的调用应该开始将流交付给客户端。 我仍然很难理解这种异步等待模式。有什么想法吗