5.1 理解并发问题
UPDATE [DBO].[TblBookList]
SET [ISBN]=@ISBN,
[BookName]=@BookName,
WHERE [ISBN]=@Original_ISBN
AND [BookName]=@Original_BookName
AND [Quantity]=@Original_Quantity
SET [ISBN]=@ISBN,
[BookName]=@BookName,
WHERE [ISBN]=@Original_ISBN
AND [BookName]=@Original_BookName
AND [Quantity]=@Original_Quantity
解决并发冲突
5.2 设计非连接数据
SCOPE_IDENTITY
INSERT INTO [TblAuthor]([SSN],[LastName],[FirstName])
VALUES(@SSN,@LastName,@FirstName);
SELECT SSN,LastName,FirstName FROM TblAuthor
WHERE (Id=SCOPE_IDENTITY());
VALUES(@SSN,@LastName,@FirstName);
SELECT SSN,LastName,FirstName FROM TblAuthor
WHERE (Id=SCOPE_IDENTITY());
如何初始化GUID

Dim WithEvents salesSurrogateGuidKeyDs As DataSet
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
For Each dt As DataTable In salesSurrogateGuidKeyDs.Tables
If Not dt.Columns("Id") Is Nothing Then
AddHandler dt.TableNewRow, AddressOf InitializeGuid
End If
Next
End Sub
Sub InitializeGuid(ByVal sender As System.Object, ByVal e As DataTableNewRowEventArgs)
If TypeOf e.Row("Id") Is DBNull Then
e.Row("Id") = Guid.NewGuid()
End If
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
For Each dt As DataTable In salesSurrogateGuidKeyDs.Tables
If Not dt.Columns("Id") Is Nothing Then
AddHandler dt.TableNewRow, AddressOf InitializeGuid
End If
Next
End Sub
Sub InitializeGuid(ByVal sender As System.Object, ByVal e As DataTableNewRowEventArgs)
If TypeOf e.Row("Id") Is DBNull Then
e.Row("Id") = Guid.NewGuid()
End If
End Sub
5.2.1 应加载什么数据
5.2.2 选择主键
5.3 GUID的用法
5.3.1 复制粘贴数据
5.3.2 在非连接表中使用同名的主键列
5.3.3 查找数据库中的GUID

CREATE PROCEDURE dbo.uspGetDataForId
(
@id uniqueidentifier
)
AS
SET NOCOUNT ON
--NOTE: this proc assume that all user tables have 'Tbl' prefix
--USAGE:in query analyser, type the following without the '--'
--EXEC uspGetDataForId 'C4831BB6-D71E-4930-BB10-88380F598479'
DECLARE @Tbl VARCHAR(2000)
DECLARE @SQL VARCHAR(2000)
IF OBJECT_ID('tempdb..#idTable')IS NOT NULL DROP TABLE #idTable
CREATE TABLE #idTable(
Id uniqueidentifier,
Count INT,
TableName VARCHAR(2000)
)
DECLARE table_cursor CURSOR
FOR SELECT TABLE_NAME FROM information_schema.TABLES
WHERE substring(TABLE_NAME,1,3)='Tbl'
OPEN TABLES_CURSOR
FETCH NEXT FROM TABLES_CURSOR INTO @Tbl
WHILE @@FETCH_STATUS=0
BEGIN
IF EXISTS(SELECT * FROM information_schema.COLUMNS
WHERE TABLE_NAME=@tbl and column_name='Id')
BEGIN
SET @SQL='INSERT INTO #idTable SELECT id as ''Id'','
+ 'COUNT(*) as ''COUNT'','''+ @tbl +'''as ''TableName'' FROM '
+ @tbl +' WHERE id=''' + CONVERT(VARCHAR(2000),@id)
+ ''' goup by id'
EXEC(@SQL)
END
FETCH NEXT FROM TABLES_CURSOR INTO @tbl
END
CLOSE TABLES_CURSOR
DEALLOCATE TABLES_CURSOR
SELECT Id, TableName FROM #idTable WHERE count > 0
(
@id uniqueidentifier
)
AS
SET NOCOUNT ON
--NOTE: this proc assume that all user tables have 'Tbl' prefix
--USAGE:in query analyser, type the following without the '--'
--EXEC uspGetDataForId 'C4831BB6-D71E-4930-BB10-88380F598479'
DECLARE @Tbl VARCHAR(2000)
DECLARE @SQL VARCHAR(2000)
IF OBJECT_ID('tempdb..#idTable')IS NOT NULL DROP TABLE #idTable
CREATE TABLE #idTable(
Id uniqueidentifier,
Count INT,
TableName VARCHAR(2000)
)
DECLARE table_cursor CURSOR
FOR SELECT TABLE_NAME FROM information_schema.TABLES
WHERE substring(TABLE_NAME,1,3)='Tbl'
OPEN TABLES_CURSOR
FETCH NEXT FROM TABLES_CURSOR INTO @Tbl
WHILE @@FETCH_STATUS=0
BEGIN
IF EXISTS(SELECT * FROM information_schema.COLUMNS
WHERE TABLE_NAME=@tbl and column_name='Id')
BEGIN
SET @SQL='INSERT INTO #idTable SELECT id as ''Id'','
+ 'COUNT(*) as ''COUNT'','''+ @tbl +'''as ''TableName'' FROM '
+ @tbl +' WHERE id=''' + CONVERT(VARCHAR(2000),@id)
+ ''' goup by id'
EXEC(@SQL)
END
FETCH NEXT FROM TABLES_CURSOR INTO @tbl
END
CLOSE TABLES_CURSOR
DEALLOCATE TABLES_CURSOR
SELECT Id, TableName FROM #idTable WHERE count > 0
5.3.4 查找数据库中所有使用GUID的位置

CREATE PROCEDURE dbo.uspGetUsagesForId
(
@id uniqueidentifier
)
AS
--NOTE: this proc assume that all user Tables have 'Tbl' prefix
--USAGE:in query analyser, type the following without the '--'
--EXEC uspGetDataForId 'C4831BB6-D71E-4930-BB10-88380F598479'
SET NOCOUNT ON
DECLARE @Tbl VARCHAR(2000)
DECLARE @sql VARCHAR(2000)
DECLARE @counter integer
IF OBJECT_ID('tempdb..#guidTable')IS NOT NULL DROP TABLE #guidTable
CREATE TABLE #guidTable(
Id uniqueidentifier,
Count INT,
TableName VARCHAR(2000)
)
DECLARE Tables_cursor CURSOR
FOR SELECT TABLE_NAME FROM information_schema.Tables
WHERE substring(TABLE_NAME,1,3)='Tbl'
OPEN Tables_cursor
FETCH NEXT FROM Tables_cursor INTO @Tbl
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql='INSERT INTO #guidTable SELECT id as ''Id'','
+ 'COUNT(*) as ''count'','''+ @Tbl +'''as ''TableName'' FROM '
+ @tbl + ' WHERE '+dbo.fnGetGuidWhereClause(@Tbl, @id)
+ ' GOUP BY ID '
EXEC(@sql)
SELECT @counter = COUNT(*) FROM #guidTable
IF @counter > 0
BEGIN
SET @sql ='SELECT ''' + @Tbl + ''' AS TABLE_NAME, * FROM ' + @Tbl
+ ' WHERE ' + dbo.fnGetGuidWhereClause(@Tbl, @id)
EXEC(@sql)
END
DELETE FROM #guidTable
FETCH NEXT FROM Tables_cursor INTO @tbl
END
CLOSE Tables_cursor
DEALLOCATE Tables_cursor
(
@id uniqueidentifier
)
AS
--NOTE: this proc assume that all user Tables have 'Tbl' prefix
--USAGE:in query analyser, type the following without the '--'
--EXEC uspGetDataForId 'C4831BB6-D71E-4930-BB10-88380F598479'
SET NOCOUNT ON
DECLARE @Tbl VARCHAR(2000)
DECLARE @sql VARCHAR(2000)
DECLARE @counter integer
IF OBJECT_ID('tempdb..#guidTable')IS NOT NULL DROP TABLE #guidTable
CREATE TABLE #guidTable(
Id uniqueidentifier,
Count INT,
TableName VARCHAR(2000)
)
DECLARE Tables_cursor CURSOR
FOR SELECT TABLE_NAME FROM information_schema.Tables
WHERE substring(TABLE_NAME,1,3)='Tbl'
OPEN Tables_cursor
FETCH NEXT FROM Tables_cursor INTO @Tbl
WHILE @@FETCH_STATUS = 0
BEGIN
SET @sql='INSERT INTO #guidTable SELECT id as ''Id'','
+ 'COUNT(*) as ''count'','''+ @Tbl +'''as ''TableName'' FROM '
+ @tbl + ' WHERE '+dbo.fnGetGuidWhereClause(@Tbl, @id)
+ ' GOUP BY ID '
EXEC(@sql)
SELECT @counter = COUNT(*) FROM #guidTable
IF @counter > 0
BEGIN
SET @sql ='SELECT ''' + @Tbl + ''' AS TABLE_NAME, * FROM ' + @Tbl
+ ' WHERE ' + dbo.fnGetGuidWhereClause(@Tbl, @id)
EXEC(@sql)
END
DELETE FROM #guidTable
FETCH NEXT FROM Tables_cursor INTO @tbl
END
CLOSE Tables_cursor
DEALLOCATE Tables_cursor
5.4 建立冲突解决界面
5.4.1 创建项目
5.4.2 扩展类型化DataSet类CustomerDataSet

Imports System.Data
Partial Class CustomerDataSet
Private createDefaultGuidForNewRows As Boolean = False
Public Sub CreateDefaultGuids()
If createDefaultGuidForNewRows Then Exit Sub
createDefaultGuidForNewRows = True
For Each dt As DataTable In Me.Tables
If Not dt.Columns("Id") Is Nothing Then
AddHandler dt.TableNewRow, AddressOf TableNewRow
End If
Next
End Sub
Private Sub TableNewRow(ByVal sender As Object, ByVal e As DataTableNewRowEventArgs)
If TypeOf e.Row("Id") Is DBNull Then
e.Row("Id") = Guid.NewGuid()
End If
End Sub
End Class
Partial Class CustomerDataSet
Private createDefaultGuidForNewRows As Boolean = False
Public Sub CreateDefaultGuids()
If createDefaultGuidForNewRows Then Exit Sub
createDefaultGuidForNewRows = True
For Each dt As DataTable In Me.Tables
If Not dt.Columns("Id") Is Nothing Then
AddHandler dt.TableNewRow, AddressOf TableNewRow
End If
Next
End Sub
Private Sub TableNewRow(ByVal sender As Object, ByVal e As DataTableNewRowEventArgs)
If TypeOf e.Row("Id") Is DBNull Then
e.Row("Id") = Guid.NewGuid()
End If
End Sub
End Class
窗体对象
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
CustomerDataSet.CreateDefaultGuids()
End Sub
CustomerDataSet.CreateDefaultGuids()
End Sub
5.4.3 扩展TableAdapter类TblCustomerTableAdapter以导出ContinueUpdateOnError属性

Namespace CustomerDataSetTableAdapters
Partial Public Class TblCustomerTableAdapter
Public Property ContinueUpdateOnError() As Boolean
Get
Return Adapter.ContinueUpdateOnError
End Get
Set(ByVal value As Boolean)
Adapter.ContinueUpdateOnError = value
End Set
End Property
End Class
End Namespace
Partial Public Class TblCustomerTableAdapter
Public Property ContinueUpdateOnError() As Boolean
Get
Return Adapter.ContinueUpdateOnError
End Get
Set(ByVal value As Boolean)
Adapter.ContinueUpdateOnError = value
End Set
End Property
End Class
End Namespace
窗体对象

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
CustomerDataSet.CreateDefaultGuids()
TblCustomerTableAdapter.ContinueUpdateOnError = True
End Sub
CustomerDataSet.CreateDefaultGuids()
TblCustomerTableAdapter.ContinueUpdateOnError = True
End Sub
5.4.4 使DataSet非连接对象与数据库服务器同步

Private Sub SyncWithDatabaseToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SyncWithDatabaseToolStripMenuItem.Click
TblCustomerTableAdapter.Update(CustomerDataSet.TblCustomer)
If CustomerDataSet.HasErrors Then
StatusStrip1.Text = "Partial synchronization with concurrency errors."
Else
'get current database data
TblCustomerTableAdapter.Fill(CustomerDataSet.TblCustomer)
StatusStrip1.Text = "Database synchronized."
End If
TblCustomerDataGridView.Refresh()
End Sub
TblCustomerTableAdapter.Update(CustomerDataSet.TblCustomer)
If CustomerDataSet.HasErrors Then
StatusStrip1.Text = "Partial synchronization with concurrency errors."
Else
'get current database data
TblCustomerTableAdapter.Fill(CustomerDataSet.TblCustomer)
StatusStrip1.Text = "Database synchronized."
End If
TblCustomerDataGridView.Refresh()
End Sub
5.4.5 创建冲突解决界面

Public Class frmConflict
Public Sub New(ByVal currentDataRow As DataRow, ByVal currentDatabaseDataRow As DataRow)
MyBase.New()
' 此调用是 Windows 窗体设计器所必需的。
InitializeComponent()
' 在 InitializeComponent() 调用之后添加任何初始化。
Me.CurrentDataRow = currentDataRow
Me.FinalDatabaseDataRow = currentDatabaseDataRow
End Sub
Public Property FinalDatabaseDataRow() As DataRow
Get
Return _FinalDatabaseDataRow
End Get
Set(ByVal value As DataRow)
_FinalDatabaseDataRow = value
End Set
End Property
Dim _FinalDatabaseDataRow As DataRow
Public Property CurrentDataRow() As DataRow
Get
Return _CurrentDataRow
End Get
Set(ByVal value As DataRow)
_CurrentDataRow = value
End Set
End Property
Dim _CurrentDataRow As DataRow
Sub PopulateTab(ByVal tab As TabPage, ByVal dataRow As DataRow, ByVal dataRowVersion As DataRowVersion, ByVal m_ReadOnly As Boolean)
Const verticalSpacing As Integer = 30
Const labelWidth As Integer = 50
Const horizontalSpacing As Integer = 10
Const buttonWidth As Integer = 100
Const buttonHeight As Integer = 20
For col As Integer = 0 To dataRow.ItemArray.Length - 1
Dim val As Object = dataRow(col, dataRowVersion)
Dim label As New Label
tab.Controls.Add(label)
label.Text = dataRow(col, dataRowVersion)
label.Top = (col + 1) * verticalSpacing
label.Width = labelWidth
label.Visible = True
Dim textbox As New TextBox
tab.Controls.Add(textbox)
With textbox
.Text = val.ToString
.Top = (col + 1) * verticalSpacing
.Left = horizontalSpacing * 2 + labelWidth
.Width = tab.Width - textbox.Left - buttonWidth - horizontalSpacing * 2
.Name = tab.Name + label.Text
.ReadOnly = m_ReadOnly
.Visible = True
.Anchor = AnchorStyles.Left Or AnchorStyles.Top Or AnchorStyles.Right
End With
If tab.Name = "tabFinal" Then Continue For
Dim btn As New Button
tab.Controls.Add(btn)
With btn
.Text = "Copy to Final"
.Left = textbox.Left + textbox.Width + horizontalSpacing
.Top = (col + 1) * verticalSpacing
.Height = buttonHeight
.Visible = True
.Anchor = AnchorStyles.Top Or AnchorStyles.Right
AddHandler .Click, AddressOf CopyToFinal
Dim propertyBag As New ArrayList
propertyBag.Add(dataRow.Table.Columns(col))
propertyBag.Add(textbox)
.Tag = propertyBag
End With
Next
End Sub
Private Sub CopyToFinal(ByVal sender As Object, ByVal e As EventArgs)
Dim btn As Button = sender
Dim propertyBag As ArrayList = CType(btn.Tag, ArrayList)
Dim dc As DataColumn = CType(propertyBag(0), DataColumn)
Dim textBox As TextBox = CType(propertyBag(1), TextBox)
TabFinal.Controls(TabFinal.Name + dc.ColumnName).Text = textBox.Text
End Sub
Private Sub frmConflict_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
PopulateTab(TabCurrent, CurrentDataRow, DataRowVersion.Current, True)
PopulateTab(TabOriginal, CurrentDataRow, DataRowVersion.Original, True)
PopulateTab(TabCurrentDb, FinalDatabaseDataRow, DataRowVersion.Original, True)
PopulateTab(TabFinal, FinalDatabaseDataRow, DataRowVersion.Current, False)
End Sub
Private Sub btnAccept_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAccept.Click
For Each dc As DataColumn In FinalDatabaseDataRow.Table.Columns
FinalDatabaseDataRow(dc) = TabFinal.Controls(TabFinal.Name + dc.ColumnName).Text
Next
End Sub
End Class
Public Sub New(ByVal currentDataRow As DataRow, ByVal currentDatabaseDataRow As DataRow)
MyBase.New()
' 此调用是 Windows 窗体设计器所必需的。
InitializeComponent()
' 在 InitializeComponent() 调用之后添加任何初始化。
Me.CurrentDataRow = currentDataRow
Me.FinalDatabaseDataRow = currentDatabaseDataRow
End Sub
Public Property FinalDatabaseDataRow() As DataRow
Get
Return _FinalDatabaseDataRow
End Get
Set(ByVal value As DataRow)
_FinalDatabaseDataRow = value
End Set
End Property
Dim _FinalDatabaseDataRow As DataRow
Public Property CurrentDataRow() As DataRow
Get
Return _CurrentDataRow
End Get
Set(ByVal value As DataRow)
_CurrentDataRow = value
End Set
End Property
Dim _CurrentDataRow As DataRow
Sub PopulateTab(ByVal tab As TabPage, ByVal dataRow As DataRow, ByVal dataRowVersion As DataRowVersion, ByVal m_ReadOnly As Boolean)
Const verticalSpacing As Integer = 30
Const labelWidth As Integer = 50
Const horizontalSpacing As Integer = 10
Const buttonWidth As Integer = 100
Const buttonHeight As Integer = 20
For col As Integer = 0 To dataRow.ItemArray.Length - 1
Dim val As Object = dataRow(col, dataRowVersion)
Dim label As New Label
tab.Controls.Add(label)
label.Text = dataRow(col, dataRowVersion)
label.Top = (col + 1) * verticalSpacing
label.Width = labelWidth
label.Visible = True
Dim textbox As New TextBox
tab.Controls.Add(textbox)
With textbox
.Text = val.ToString
.Top = (col + 1) * verticalSpacing
.Left = horizontalSpacing * 2 + labelWidth
.Width = tab.Width - textbox.Left - buttonWidth - horizontalSpacing * 2
.Name = tab.Name + label.Text
.ReadOnly = m_ReadOnly
.Visible = True
.Anchor = AnchorStyles.Left Or AnchorStyles.Top Or AnchorStyles.Right
End With
If tab.Name = "tabFinal" Then Continue For
Dim btn As New Button
tab.Controls.Add(btn)
With btn
.Text = "Copy to Final"
.Left = textbox.Left + textbox.Width + horizontalSpacing
.Top = (col + 1) * verticalSpacing
.Height = buttonHeight
.Visible = True
.Anchor = AnchorStyles.Top Or AnchorStyles.Right
AddHandler .Click, AddressOf CopyToFinal
Dim propertyBag As New ArrayList
propertyBag.Add(dataRow.Table.Columns(col))
propertyBag.Add(textbox)
.Tag = propertyBag
End With
Next
End Sub
Private Sub CopyToFinal(ByVal sender As Object, ByVal e As EventArgs)
Dim btn As Button = sender
Dim propertyBag As ArrayList = CType(btn.Tag, ArrayList)
Dim dc As DataColumn = CType(propertyBag(0), DataColumn)
Dim textBox As TextBox = CType(propertyBag(1), TextBox)
TabFinal.Controls(TabFinal.Name + dc.ColumnName).Text = textBox.Text
End Sub
Private Sub frmConflict_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
PopulateTab(TabCurrent, CurrentDataRow, DataRowVersion.Current, True)
PopulateTab(TabOriginal, CurrentDataRow, DataRowVersion.Original, True)
PopulateTab(TabCurrentDb, FinalDatabaseDataRow, DataRowVersion.Original, True)
PopulateTab(TabFinal, FinalDatabaseDataRow, DataRowVersion.Current, False)
End Sub
Private Sub btnAccept_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAccept.Click
For Each dc As DataColumn In FinalDatabaseDataRow.Table.Columns
FinalDatabaseDataRow(dc) = TabFinal.Controls(TabFinal.Name + dc.ColumnName).Text
Next
End Sub
End Class
5.4.6 调用冲突解决界面

Private Sub ResolveConcurrencyErrorsToolStripMenuItem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles ResolveConcurrencyErrorsToolStripMenuItem.Click
If CustomerDataSet.TblCustomer.HasErrors Then
Dim refreshCustomer As New CustomerDataSet
TblCustomerTableAdapter.Fill(refreshCustomer.TblCustomer)
For Each dr As DataRow In CustomerDataSet.TblCustomer.GetErrors
Dim currentDb As DataRow = refreshCustomer.TblCustomer.Rows.Find(dr("Id"))
Using conflict As New frmConflict(dr, currentDb)
If conflict.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
dr.ClearErrors()
TblCustomerTableAdapter.Update(conflict.FinalDatabaseDataRow)
CustomerDataSet.TblCustomer.LoadDataRow( _
conflict.FinalDatabaseDataRow.ItemArray, LoadOption.OverwriteChanges)
StatusStrip1.Text = "Single row updated. "
Else
StatusStrip1.Text = "Single row update cancelled."
End If
End Using
Next
TblCustomerDataGridView.Refresh()
End If
End Sub
Handles ResolveConcurrencyErrorsToolStripMenuItem.Click
If CustomerDataSet.TblCustomer.HasErrors Then
Dim refreshCustomer As New CustomerDataSet
TblCustomerTableAdapter.Fill(refreshCustomer.TblCustomer)
For Each dr As DataRow In CustomerDataSet.TblCustomer.GetErrors
Dim currentDb As DataRow = refreshCustomer.TblCustomer.Rows.Find(dr("Id"))
Using conflict As New frmConflict(dr, currentDb)
If conflict.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
dr.ClearErrors()
TblCustomerTableAdapter.Update(conflict.FinalDatabaseDataRow)
CustomerDataSet.TblCustomer.LoadDataRow( _
conflict.FinalDatabaseDataRow.ItemArray, LoadOption.OverwriteChanges)
StatusStrip1.Text = "Single row updated. "
Else
StatusStrip1.Text = "Single row update cancelled."
End If
End Using
Next
TblCustomerDataGridView.Refresh()
End If
End Sub
5.4.7 使用冲突解决界面改正并发错误
5.4.8 建立一个更好的冲突解决界面