我尝试利用WebAPI将大量数据从SQL Server流式传输到分隔文件,而无需等待整个结果集从数据库返回。我对Async和Await非常可怕,并没有把我的头包裹起来。看来我应该使用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
我的存储库有一个签名为: 函数ExportAsync(id As String,responseStream As IO.Stream)As Task
最后,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
客户端函数似乎正确退出并最终返回响应。对浏览器的最终响应确实看起来像一个附件,它是空的,我吮吸流,但似乎它不是以那种神奇的异步方式发生的。
另外,在我看来,这是一个有点简单的练习,虽然我无法在网上找到一个好的实现。也许我真的错了。
答案 0 :(得分:0)
我发现了一些曾经/不起作用的东西。
<强>更新强>
存储库方法
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关闭(End Using语句)之前,下载不会在客户端启动。我的理解是PushStreamContent应该在遇到它的第一次Await时返回,随后对FlushAsync的调用应该开始将流传递给客户端。 我仍然很难理解这个Async Await模式。有什么想法吗?