保存DataSet和DataTable的方法大同小异,以下仅以保存DataTable为例。
作者:魏滔序
声明:转载请保留作者信息并注明出处。
在数据库中创建一个用来测试的表:
CREATE TABLE [dbo].[MyTable](
[c1] [int] NOT NULL,
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED
(
[c1] ASC
)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
设计模式:
查看模式:
通常情况下,我们保存一个Table的数据的方法如下:
Private Sub SaveData(ByVal Table As DataTable)
Dim ConnectionText As String = "...代码略..."
Dim SQL As String = "SELECT * FROM MyTable"
Dim MyCn As OleDbConnection = New OleDbConnection(ConnectionText)
Try
Dim MyCmd As OleDbCommand = New OleDbCommand(SQL, MyCn)
Dim MyDa As OleDbDataAdapter = New OleDbDataAdapter(MyCmd)
Dim MyCb As OleDbCommandBuilder = New OleDbCommandBuilder(MyDa)
MyDa.Update(Table)
Catch ex As Exception
MsgBox(ex.Message, 16, "错误")
End Try
End Sub
这种方法看似没什么大问题,网络上的很多资料也如出一辙。
我们要求的是数据完整性,当在Table中添加了重复的记录并保存,由于C1列是主键,此时一定会抛出异常。
这个错误抛出的理所当然。看看数据库表中保存的结果,第一条成功保存了。
按照常理,我们此时要做的就是把第二条数据修正一下再保存即可完事大吉。但是如果此时用户退出了程序问题就出来了,数据库中仅保存了第一条记录,出现了“半拉子”工程,显然不是我们想看到的。那么,如果加入一个事务(Transaction)处理呢?利用他的回滚(Rollback)机制看似可以很好的解决这个问题。
代码:
Private Sub SaveData(ByVal Table As DataTable)
Dim ConnectionText As String = "...代码略..."
Dim SQL As String = "SELECT * FROM MyTable"
Dim MyCn As OleDbConnection = New OleDbConnection(ConnectionText)
Dim MyTran As OleDbTransaction
MyCn.Open()
MyTran = MyCn.BeginTransaction()
Try
Dim MyCmd As OleDbCommand = New OleDbCommand(SQL, MyCn)
MyCmd.Transaction = MyTran
Dim MyDa As OleDbDataAdapter = New OleDbDataAdapter(MyCmd)
Dim MyCb As OleDbCommandBuilder = New OleDbCommandBuilder(MyDa)
MyDa.Update(Table)
MyTran.Commit()
Catch ex As Exception
MyTran.Rollback()
MsgBox(ex.Message, 16, "错误")
End Try
MyCn.Close()
End Sub
清空数据库表中的数据,还是同样的测试方法,输入重复的值。结果抛出了和先前完全一样的异常(这句好像废话)。
这次数据表中果然没有数据被保存,基本上达到了我们的要求。
接下来我们把第二条数据修改为和第一条不同的数字再试试,比如修改为2,然后保存。程序的执行通过了。
我们再看看数据库表中的数据。
My God,竟然只保存了后来被修改的第二条记录。第一条数据哪里去了?怪哉!!
原来,在第一次执行MyDa.Update(Table)的时候,第一条数据是正常的,行状态在保存时被自动设置为Unchanged。但由于第二条出错,导致本该保存到数据库的第一条数据被回滚,这也没问题。问题就出在事务虽然回滚了,但被修改的行状态没有撤销,依然是修改后的Unchanged。当我们修改完第二条后再执行MyDa.Update(Table),非Unchanged状态的数据保存到了数据库,第一条就被无辜的遗漏了。
既然Table中的行状态会在保存的过程中被修改,那我们就先复制一个副本,对副本进行保存,副本的行状态爱怎么变就怎么变,我们不关心了(这个想法好像有点消极)。如果没问题则再对原始Table执行AcceptChanges。
代码:
Private Sub SaveData(ByVal Table As DataTable)
Dim ConnectionText As String = "...代码略..."
Dim SQL As String = "SELECT * FROM MyTable"
Dim MyCn As OleDbConnection = New OleDbConnection(ConnectionText)
Dim MyTran As OleDbTransaction
MyCn.Open()
MyTran = MyCn.BeginTransaction()
Try
Dim MyCmd As OleDbCommand = New OleDbCommand(SQL, MyCn)
MyCmd.Transaction = MyTran
Dim MyDa As OleDbDataAdapter = New OleDbDataAdapter(MyCmd)
Dim MyCb As OleDbCommandBuilder = New OleDbCommandBuilder(MyDa)
Dim TabCopy As DataTable = Table.Copy
MyDa.Update(TabCopy)
MyTran.Commit()
Table.AcceptChanges()
Catch ex As Exception
MyTran.Rollback()
MsgBox(ex.Message, 16, "错误")
End Try
MyCn.Close()
End Sub
这里注意,复制Table要用DataTable的Copy函数,直接赋值并非真正的复制,另外DataTable的Clone函数出来的副本丢失了行状态,全部是Unchanged。
再测试一把,还是基本达到了我们的要求。但有两个不足,第一是内存资源稍微多耗一点点,第二是无法在DataGridView上标记出错误的行,就是前面的那个小红色图标。因为抛出异常的是DataGridView的DataSource的副本,跟DataGridView没有任何关系。
能不能做的更好呢?能!我们尝试从OleDbDataAdapter下手,粗略一看竟然发现了一个伟大的属性:AcceptChangesDuringUpdate,MSDN中是这样描述的:获取或设置在 System.Data.Common.DataAdapter.Update(System.Data.DataSet) 期间是否调用 System.Data.DataRow.AcceptChanges。
写代码继续测试之:
Private Sub SaveData(ByVal Table As DataTable)
Dim ConnectionText As String = "...代码略..."
Dim SQL As String = "SELECT * FROM MyTable"
Dim MyCn As OleDbConnection = New OleDbConnection(ConnectionText)
Dim MyTran As OleDbTransaction
MyCn.Open()
MyTran = MyCn.BeginTransaction()
Try
Dim MyCmd As OleDbCommand = New OleDbCommand(SQL, MyCn)
MyCmd.Transaction = MyTran
Dim MyDa As OleDbDataAdapter = New OleDbDataAdapter(MyCmd)
Dim MyCb As OleDbCommandBuilder = New OleDbCommandBuilder(MyDa)
MyDa.AcceptChangesDuringUpdate = False
MyTran.Commit()
Table.AcceptChanges()
Catch ex As Exception
MyTran.Rollback()
MsgBox(ex.Message, 16, "错误")
End Try
MyCn.Close()
End Sub
测试结果是那么那么的优雅,那么那么的完美,大道至简也不过如此。(完)