好的,对于那些可能感兴趣的人,我终于设法解决了这个问题 -
我的测试代码如下。
有一个包含各种“数据库”例程的模块,以及一个调用这些例程的表单(请注意,表单代码只是代码,不是实际的表单,但应该足以演示它是如何工作的)。
通过使用代码中显示的事务和锁定提示,我可以读取记录并将其锁定,以便其他用户无法访问它。我可以应用更新,一旦应用提交(或中止)事务,记录就会为其他用户解锁。
尝试访问锁定记录的第二个用户/实例将被“暂停”,直到第一个实例的事务完成(尽管这将在 30 秒后超时,但这由代码处理)。
第二个实例可以访问不同的记录并根据需要对其进行更新。
模块代码:
Option Explicit On
Imports System
Imports System.Data.SqlClient
Public Module DataAccess
Public m_sConnectionString As String = "Server=sql01; Database=test; uid=myuser; pwd=mypwd;"
Private m_oConnection As SqlConnection = New SqlConnection
Private m_oTransaction As SqlTransaction
Public Function OpenDB() As Boolean
Try
m_oConnection = New SqlConnection
m_oConnection.ConnectionString = m_sConnectionString
m_oConnection.Open()
Return True
Catch sqlex As SqlException
Console.WriteLine(sqlex.Message)
Return False
Catch ex As Exception
Console.WriteLine(ex.Message)
Return False
End Try
End Function
Public Sub CloseDB()
Try
m_oTransaction = Nothing
m_oConnection.Close()
m_oConnection = Nothing
Catch sqlex As SqlException
Console.WriteLine(sqlex.Message)
Stop
Catch ex As Exception
Console.WriteLine(ex.Message)
Stop
Finally
End Try
End Sub
Public Sub CommitTransaction()
Try
If m_oTransaction IsNot Nothing Then m_oTransaction.Commit()
Catch sqlex As SqlException
Console.WriteLine(sqlex.Message)
Stop
Catch ex As Exception
Console.WriteLine(ex.Message)
Stop
Finally
m_oTransaction = Nothing
End Try
End Sub
Public Sub AbortTransaction()
Try
If m_oTransaction IsNot Nothing Then m_oTransaction.Rollback()
Catch sqlex As SqlException
Console.WriteLine(sqlex.Message)
Stop
Catch ex As Exception
Console.WriteLine(ex.Message)
Stop
Finally
m_oTransaction = Nothing
End Try
End Sub
Public Function ReadRecordByID(ID As Integer, LockRecord As Boolean) As Tuple(Of Boolean, DataRow)
Dim Result As Tuple(Of Boolean, DataRow) = New Tuple(Of Boolean, DataRow)(False, Nothing)
If ID = 0 Then Return Result
Dim sSQL As String = ""
Dim oCommand As SqlCommand = Nothing
Dim LockCommand As String = ""
Dim MyDA As SqlDataAdapter
Dim MyTable As DataTable
Dim MyRow As DataRow
Try
m_oTransaction = Nothing
oCommand = m_oConnection.CreateCommand
If LockRecord Then
m_oTransaction = m_oConnection.BeginTransaction(IsolationLevel.Serializable)
LockCommand = "WITH (HOLDLOCK, UPDLOCK) "
End If
sSQL = ""
sSQL &= "SELECT * FROM TempStock " & LockCommand & "WHERE StockID = " & ID
oCommand.CommandText = sSQL
oCommand.Connection = m_oConnection
MyDA = New SqlDataAdapter
MyTable = New DataTable
If LockRecord Then oCommand.Transaction = m_oTransaction
MyDA.SelectCommand = oCommand
MyDA.Fill(MyTable)
MyRow = MyTable.Rows(0)
Result = New Tuple(Of Boolean, DataRow)(True, MyRow)
MyTable = Nothing
MyDA = Nothing
Catch sqlex As SqlException
Console.WriteLine(sqlex.Message)
If m_oTransaction IsNot Nothing Then AbortTransaction()
Catch ex As Exception
Console.WriteLine(ex.Message)
If m_oTransaction IsNot Nothing Then AbortTransaction()
Finally
End Try
Return Result
End Function
Public Function UpdateRecord(Row As DataRow) As Boolean
Dim Result As Boolean = False
Dim sSQL As String = ""
Dim oCommand As SqlCommand = Nothing
Try
oCommand = m_oConnection.CreateCommand
sSQL = ""
sSQL &= "UPDATE TempStock " & vbCrLf
sSQL &= "SET Quantity = " & Row("Quantity") & ", Allocated = " & Row("Allocated") & ", LastUpdated = GETDATE() WHERE StockID = " & Row("StockID")
oCommand.CommandText = sSQL
oCommand.Connection = m_oConnection
oCommand.Transaction = m_oTransaction
oCommand.ExecuteNonQuery()
Result = True
Catch sqlex As SqlException
Console.WriteLine(sqlex.Message)
If m_oTransaction IsNot Nothing Then AbortTransaction()
Result = False
Catch ex As Exception
Console.WriteLine(ex.Message)
If m_oTransaction IsNot Nothing Then AbortTransaction()
Result = False
End Try
Return Result
End Function
End Module
表格代码:
Option Explicit On
Imports System
Public Class Form1
Private MyDataRow As DataRow
Private Sub btnOpen_Click(sender As Object, e As EventArgs) Handles btnOpen.Click
Dim ID As Integer = Val(txtID.Text)
If ID < 1 Then Exit Sub
btnOpen.Enabled = False
btnUpdate.Enabled = False
btnCommit.Enabled = False
btnAbort.Enabled = False
If OpenDB() Then
TryAgain:
Dim ReadRow As Tuple(Of Boolean, DataRow) = ReadRecordByID(ID, chkTransaction.Checked)
btnUpdate.Enabled = True
If ReadRow.Item1 Then
MyDataRow = ReadRow.Item2
lblQty.Text = MyDataRow("Quantity")
lblAlloc.Text = MyDataRow("Allocated")
txtQty.Text = ""
txtAlloc.Text = ""
If Not chkTransaction.Checked Then btnAbort.Enabled = True
Else
Select Case MessageBox.Show("Transaction Time Out - Unable to lock record", "Database", MessageBoxButtons.RetryCancel, MessageBoxIcon.Question)
Case DialogResult.Retry
GoTo TryAgain
Case Else
End Select
btnOpen.Enabled = True
btnUpdate.Enabled = False
btnCommit.Enabled = False
btnAbort.Enabled = False
txtID.Select()
End If
Else
btnOpen.Enabled = True
btnUpdate.Enabled = False
btnCommit.Enabled = False
btnAbort.Enabled = False
End If
End Sub
Private Sub btnUpdate_Click(sender As Object, e As EventArgs) Handles btnUpdate.Click
MyDataRow("Quantity") += Val(txtQty.Text)
MyDataRow("Allocated") += Val(txtAlloc.Text)
If UpdateRecord(MyDataRow) Then
If chkTransaction.Checked Then
btnCommit.Enabled = True
btnAbort.Enabled = True
btnUpdate.Enabled = False
Else
btnOpen.Enabled = True
btnCommit.Enabled = False
btnAbort.Enabled = False
btnUpdate.Enabled = False
lblQty.Text = ""
lblAlloc.Text = ""
txtQty.Text = ""
txtAlloc.Text = ""
End If
Else
btnOpen.Enabled = True
btnUpdate.Enabled = False
btnCommit.Enabled = False
btnAbort.Enabled = False
lblQty.Text = ""
lblAlloc.Text = ""
txtQty.Text = ""
txtAlloc.Text = ""
End If
End Sub
Private Sub btnCommit_Click(sender As Object, e As EventArgs) Handles btnCommit.Click
CommitTransaction()
lblQty.Text = ""
lblAlloc.Text = ""
txtQty.Text = ""
txtAlloc.Text = ""
btnOpen.Enabled = True
btnCommit.Enabled = False
btnAbort.Enabled = False
CloseDB()
End Sub
Private Sub btnAbort_Click(sender As Object, e As EventArgs) Handles btnAbort.Click
AbortTransaction()
lblQty.Text = ""
lblAlloc.Text = ""
txtQty.Text = ""
txtAlloc.Text = ""
btnOpen.Enabled = True
btnUpdate.Enabled = False
btnCommit.Enabled = False
btnAbort.Enabled = False
CloseDB()
End Sub
End Class
为了让它发挥作用,我进行了很多试验和错误,因为我找不到任何示例,只是以模糊的方式涵盖了理论的文档。
我确实让它工作了,然后它停止了 - 我更改了数据库表 - 你必须在表上有一个主键,并且有 'allow row locks = true'。
我希望这对处于类似情况的任何人有所帮助。