如何将 BLOB 从 vb.net winforms 应用程序流式传输到 SQL 服务器并报告进度
How can I stream a BLOB from vb.net winforms application to SQL Server and report progress
我正在尝试将文件(可能很大)从我的 VB.Net Winforms 应用程序上传到 SQL Server 2008(C# 答案也可以接受)。
文件使用 SQL 服务器的 FILESTREAM 数据类型存储为 Varbinary(MAX)。
我通过传入一个 FileStream 作为 SqlParameter 将文件发送到服务器。
这没问题。但是,由于大文件需要一段时间才能上传,我想将进度报告回 UI 上的 ProgressBar。
我很确定我需要使用 Async/Await。主要问题实际上是获取进度值。因为我没有对 FileStream 做任何事情,只是将它作为 SqlParameter 传递,所以我不知道如何才能取回进度值。我怎样才能做到这一点?
我考虑过将流复制到另一个流并在那里获取进度值,但我认为这意味着将整个文件读入内存,我什至不确定它是否有效。
是否有 FileStream 的 Async 方法可以满足我的需要?还是有更好的方法让我做到这一点?
谢谢。
只是想让你知道我做了什么来解决这个问题...
我很清楚这个解决方案远非理想。一定有更好的方法,我仍在寻求改进它……但目前,它似乎可以满足我的需要。如有任何意见或建议,我们将不胜感激。
请在下面查看我简化的、完整注释的代码:
//Create the FileStream
Using SourceStream As New FileStream("PathToTheFile", FileMode.Open)
//Set the max value of your ProgressBar to the length of the stream
ProgressPb.Maximum = SourceStream.Length
//Start the task of sending the file to the DB (saving a reference to the task for later use.
Dim FileUpload As Task(Of Boolean) = Task.Run(Function() SendToDB())
//declare a variable to hold the last known position in the stream
Dim LastPosition As Long = 0
//loop until we we are done (until the current position is at the end of the stream)
Do
//only do something if the position in the strang has changed since we last checked.
If SourceStream.Position <> LastPosition Then
//save the current position for next time
LastPosition = SourceStream.Position
//set the value of your progress bar to the current position in the stream
ProgressPb.Value = SourceStream.Position
//set your status label text as you wish
StatusLbl.Text = "Uploading: " & Math.Round(SourceStream.Position / 1048576) & "MB of " & Math.Round(SourceStream.Length / 1048576) & "MB"
//call do events to save locking up your UI
Application.DoEvents()
End If
//limit the checks (8 times per second seems reasonably responsive)
Threading.Thread.Sleep(1000 / 8)
Loop Until LastPosition = SourceStream.Position
//set your status label text as "Finalising" or similar (there is a short delay of 1-2 seconds after we reach the end of the stream but before we recieve a response back from the DB).
StatusLbl.Text = "Uploading: " & Math.Round(SourceStream.Position / 1048576) & "MB of " & Math.Round(SourceStream.Length / 1048576) & "MB Complete. Finalising..."
//wait for the response from the database
Res = Await FileUpload
//set your status label text "Complete" or similar
StatusLbl.Text = "Uploading: " & Math.Round(SourceStream.Position / 1048576) & "MB of " & Math.Round(SourceStream.Length / 1048576) & "MB Complete. Finalising... Complete!"
//check the result from the database and do stuff accordingly.
If Res Then
MsgBox("Success")
Else
MsgBox("Failed")
End If
End Using
编辑
只是一个更新。我已将代码重构为 "StreamWatcher" class。请参阅下面 class 的代码:
Imports System.IO
Public Class StreamWatcher
Private Const MaxProgressEventsPerSecond As Integer = 200
Private Property _Stream As Stream
Private Property _PreviousPosision As Long
Public ReadOnly Property StreamPosition As Long
Get
If IsNothing(_Stream) Then
Return 0
Else
Return _Stream.Position
End If
End Get
End Property
Public ReadOnly Property StreamLength As Long
Get
If IsNothing(_Stream) Then
Return 0
Else
Return _Stream.Length
End If
End Get
End Property
Private Property _TimeStarted As DateTime? = Nothing
Private Property _TimeFinished As DateTime? = Nothing
Public ReadOnly Property SecondsTaken As Double
Get
If IsNothing(_TimeStarted) Then
Return 0.0
Else
If IsNothing(_TimeFinished) Then
Return (DateTime.Now - _TimeStarted.Value).TotalSeconds
Else
Return (_TimeFinished.Value - _TimeStarted.Value).TotalSeconds
End If
End If
End Get
End Property
Private Property _UpdatesCalled As Integer = 0
Public ReadOnly Property Updates As Integer
Get
Return _UpdatesCalled
End Get
End Property
Private Property _Progress As IProgress(Of EventType)
Private Enum EventType
Progressed
Completed
End Enum
Public Event Progressed()
Public Event Completed()
Public Sub Watch(ByRef StreamToWatch As Stream)
Reset()
_Stream = StreamToWatch
Dim ProgressHandler As New Progress(Of EventType)(Sub(Value) EventRaiser(Value))
_Progress = TryCast(ProgressHandler, IProgress(Of EventType))
_TimeStarted = DateTime.Now
Task.Run(Sub() MonitorStream())
End Sub
Private Sub MonitorStream()
Do
If _PreviousPosision <> StreamPosition Then
_PreviousPosision = StreamPosition
_Progress.Report(EventType.Progressed)
//limit events to max events per second
Task.Delay(1000 / MaxProgressEventsPerSecond).Wait()
End If
Loop Until (Not IsNothing(_Stream)) AndAlso (StreamPosition = StreamLength)
_TimeFinished = DateTime.Now
_Progress.Report(EventType.Completed)
End Sub
Private Sub EventRaiser(ByVal EventToRaise As EventType)
Select Case EventToRaise
Case EventType.Progressed
_UpdatesCalled += 1
RaiseEvent Progressed()
Case EventType.Completed
_UpdatesCalled += 1
RaiseEvent Completed()
End Select
End Sub
Private Sub Reset()
_Stream = Nothing
_PreviousPosision = 0
_TimeStarted = Nothing
_TimeFinished = Nothing
_UpdatesCalled = 0
End Sub
End Class
用法是这样的:
Private WithEvents StreamWatcher As New StreamWatcher
Private Async Sub SaveBtn_Click(sender As Object, e As EventArgs) Handles SaveBtn.Click
Dim Res As Boolean
Using FS As New FileStream("MyFilePath", FileMode.Open)
//this attaches the the watcher to the stream
StreamWatcher.Watch(FS)
ProgressBar.Maximum = FS.Length
//This waits for the function to finish before continuing, without locking up the UI... be careful not to dispose the stream before it's finished.
Res = Await Task.Run(Function() MySendToDBFunction(MyParam1))
StatusLbl.Text += " Complete!"
End Using
If Res Then
MessageBox.Show("Success")
Else
MessageBox.Show("Fail")
End If
End Sub
//Called whenever the position in the stream changes (upto the max calls per second as defined in the StreamWatcher class - 200 seems to be more than enough))
Private Sub FSWithProgeress_Progressed() Handles StreamWatcher.Progressed
ProgressBar.Value = StreamWatcher.StreamPosition
StatusLbl.Text = "Uploading: " & Math.Round(StreamWatcher.StreamPosition / 1048576) & "MB of " & Math.Round(StreamWatcher.StreamLength / 1048576) & "MB"
End Sub
//called once the end of the stream is reached
Private Sub FSWithProgeress_Completed() Handles StreamWatcher.Completed
StatusLbl.Text = "Upload: Complete. Finalising..."
End Sub
我正在尝试将文件(可能很大)从我的 VB.Net Winforms 应用程序上传到 SQL Server 2008(C# 答案也可以接受)。
文件使用 SQL 服务器的 FILESTREAM 数据类型存储为 Varbinary(MAX)。
我通过传入一个 FileStream 作为 SqlParameter 将文件发送到服务器。
这没问题。但是,由于大文件需要一段时间才能上传,我想将进度报告回 UI 上的 ProgressBar。
我很确定我需要使用 Async/Await。主要问题实际上是获取进度值。因为我没有对 FileStream 做任何事情,只是将它作为 SqlParameter 传递,所以我不知道如何才能取回进度值。我怎样才能做到这一点?
我考虑过将流复制到另一个流并在那里获取进度值,但我认为这意味着将整个文件读入内存,我什至不确定它是否有效。
是否有 FileStream 的 Async 方法可以满足我的需要?还是有更好的方法让我做到这一点?
谢谢。
只是想让你知道我做了什么来解决这个问题...
我很清楚这个解决方案远非理想。一定有更好的方法,我仍在寻求改进它……但目前,它似乎可以满足我的需要。如有任何意见或建议,我们将不胜感激。
请在下面查看我简化的、完整注释的代码:
//Create the FileStream
Using SourceStream As New FileStream("PathToTheFile", FileMode.Open)
//Set the max value of your ProgressBar to the length of the stream
ProgressPb.Maximum = SourceStream.Length
//Start the task of sending the file to the DB (saving a reference to the task for later use.
Dim FileUpload As Task(Of Boolean) = Task.Run(Function() SendToDB())
//declare a variable to hold the last known position in the stream
Dim LastPosition As Long = 0
//loop until we we are done (until the current position is at the end of the stream)
Do
//only do something if the position in the strang has changed since we last checked.
If SourceStream.Position <> LastPosition Then
//save the current position for next time
LastPosition = SourceStream.Position
//set the value of your progress bar to the current position in the stream
ProgressPb.Value = SourceStream.Position
//set your status label text as you wish
StatusLbl.Text = "Uploading: " & Math.Round(SourceStream.Position / 1048576) & "MB of " & Math.Round(SourceStream.Length / 1048576) & "MB"
//call do events to save locking up your UI
Application.DoEvents()
End If
//limit the checks (8 times per second seems reasonably responsive)
Threading.Thread.Sleep(1000 / 8)
Loop Until LastPosition = SourceStream.Position
//set your status label text as "Finalising" or similar (there is a short delay of 1-2 seconds after we reach the end of the stream but before we recieve a response back from the DB).
StatusLbl.Text = "Uploading: " & Math.Round(SourceStream.Position / 1048576) & "MB of " & Math.Round(SourceStream.Length / 1048576) & "MB Complete. Finalising..."
//wait for the response from the database
Res = Await FileUpload
//set your status label text "Complete" or similar
StatusLbl.Text = "Uploading: " & Math.Round(SourceStream.Position / 1048576) & "MB of " & Math.Round(SourceStream.Length / 1048576) & "MB Complete. Finalising... Complete!"
//check the result from the database and do stuff accordingly.
If Res Then
MsgBox("Success")
Else
MsgBox("Failed")
End If
End Using
编辑
只是一个更新。我已将代码重构为 "StreamWatcher" class。请参阅下面 class 的代码:
Imports System.IO
Public Class StreamWatcher
Private Const MaxProgressEventsPerSecond As Integer = 200
Private Property _Stream As Stream
Private Property _PreviousPosision As Long
Public ReadOnly Property StreamPosition As Long
Get
If IsNothing(_Stream) Then
Return 0
Else
Return _Stream.Position
End If
End Get
End Property
Public ReadOnly Property StreamLength As Long
Get
If IsNothing(_Stream) Then
Return 0
Else
Return _Stream.Length
End If
End Get
End Property
Private Property _TimeStarted As DateTime? = Nothing
Private Property _TimeFinished As DateTime? = Nothing
Public ReadOnly Property SecondsTaken As Double
Get
If IsNothing(_TimeStarted) Then
Return 0.0
Else
If IsNothing(_TimeFinished) Then
Return (DateTime.Now - _TimeStarted.Value).TotalSeconds
Else
Return (_TimeFinished.Value - _TimeStarted.Value).TotalSeconds
End If
End If
End Get
End Property
Private Property _UpdatesCalled As Integer = 0
Public ReadOnly Property Updates As Integer
Get
Return _UpdatesCalled
End Get
End Property
Private Property _Progress As IProgress(Of EventType)
Private Enum EventType
Progressed
Completed
End Enum
Public Event Progressed()
Public Event Completed()
Public Sub Watch(ByRef StreamToWatch As Stream)
Reset()
_Stream = StreamToWatch
Dim ProgressHandler As New Progress(Of EventType)(Sub(Value) EventRaiser(Value))
_Progress = TryCast(ProgressHandler, IProgress(Of EventType))
_TimeStarted = DateTime.Now
Task.Run(Sub() MonitorStream())
End Sub
Private Sub MonitorStream()
Do
If _PreviousPosision <> StreamPosition Then
_PreviousPosision = StreamPosition
_Progress.Report(EventType.Progressed)
//limit events to max events per second
Task.Delay(1000 / MaxProgressEventsPerSecond).Wait()
End If
Loop Until (Not IsNothing(_Stream)) AndAlso (StreamPosition = StreamLength)
_TimeFinished = DateTime.Now
_Progress.Report(EventType.Completed)
End Sub
Private Sub EventRaiser(ByVal EventToRaise As EventType)
Select Case EventToRaise
Case EventType.Progressed
_UpdatesCalled += 1
RaiseEvent Progressed()
Case EventType.Completed
_UpdatesCalled += 1
RaiseEvent Completed()
End Select
End Sub
Private Sub Reset()
_Stream = Nothing
_PreviousPosision = 0
_TimeStarted = Nothing
_TimeFinished = Nothing
_UpdatesCalled = 0
End Sub
End Class
用法是这样的:
Private WithEvents StreamWatcher As New StreamWatcher
Private Async Sub SaveBtn_Click(sender As Object, e As EventArgs) Handles SaveBtn.Click
Dim Res As Boolean
Using FS As New FileStream("MyFilePath", FileMode.Open)
//this attaches the the watcher to the stream
StreamWatcher.Watch(FS)
ProgressBar.Maximum = FS.Length
//This waits for the function to finish before continuing, without locking up the UI... be careful not to dispose the stream before it's finished.
Res = Await Task.Run(Function() MySendToDBFunction(MyParam1))
StatusLbl.Text += " Complete!"
End Using
If Res Then
MessageBox.Show("Success")
Else
MessageBox.Show("Fail")
End If
End Sub
//Called whenever the position in the stream changes (upto the max calls per second as defined in the StreamWatcher class - 200 seems to be more than enough))
Private Sub FSWithProgeress_Progressed() Handles StreamWatcher.Progressed
ProgressBar.Value = StreamWatcher.StreamPosition
StatusLbl.Text = "Uploading: " & Math.Round(StreamWatcher.StreamPosition / 1048576) & "MB of " & Math.Round(StreamWatcher.StreamLength / 1048576) & "MB"
End Sub
//called once the end of the stream is reached
Private Sub FSWithProgeress_Completed() Handles StreamWatcher.Completed
StatusLbl.Text = "Upload: Complete. Finalising..."
End Sub