Vb.net PushStreamContent使用SqlDataReader.ExecuteReaderAsync将CSV或分隔文件流式传输到客户端
我试图利用WebAPI将大量数据从SQL Server流式传输到一个分隔文件,而不必等待整个结果集从数据库返回。我对Async和Wait的使用非常糟糕,我还没有完全理解它。似乎我想使用PushStreamContent和SqlDataReader异步方法来实现。 这是我的DataAccessLayer: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
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的调用应该开始将流交付给客户端。
我仍然很难理解这种异步等待模式。有什么想法吗