在《数据集DataSet数据管理(DataAdapter查询更新数据库)》一文中提到可以使用OleDbDataAdapter对象的Update方法还可将对 DataSet 所做的更改解析回数据源,实例化OleDbDataAdapter对象时必须设置InsertCommand、 UpdateCommand或 DeleteCommand属性。那么OleDbDataAdapter对象是如何知道DataSet 数据集中哪些数据是要插入的、哪些是更改过的、而哪些又是需要删除的呢?
ADO.NET 用行状态和行版本管理表中的行。 行状态指示行的状态;行版本在修改行中存储的值时维护各个阶段的值,包括当前值、原始值和默认值。
一、行状态
(一)、RowState枚举
每个 DataRow 对象都具有 RowState 属性,您可以检查此属性来确定行的当前状态。只要该行中某一个字段发生改变,那么这一行对应的RowState属性就会发生改变。RowState是一个枚举,其中包含5个内容:
1、Unchanged
自上次调用 AcceptChanges 以来或由 DataAdapter.Fill 创建该行以来,没有进行任何更改。——原来的行!
2、Added
已将该行添加到表中,但尚未调用 AcceptChanges。——插入的新行!
3、Modified
已更改了行的某个元素。——刚被修改过的行!
4、Deleted
已从表中删除该行,并且尚未调用 AcceptChanges。——刚被删除的行(注意:这里只是从内存表中删除,准确的说是该行只是被做了Deleted标志并没有真的删除,物理数据表中更是没有删除,所以此时你无法直接去访问该行的某个字段的内容!)。
5、Detached
该行不是表中的行——该行不是任何 DataRowCollection 的一部分。 新创建的行、或者已经被Remove或者RemoveAt、或者Delete之后调用过AcceptChanges方法的行、或者是WinForm控件DataGridView默认设置下最后那个永远也留出的空行……都被自动设置该状态。这种状态表示已初始化但未添加到DataTable中的数据行,此状态我们不必太关心。
在使用OleDbDataAdapter对象的Update方法时,将会检查DataTable中每一行的状态,然后调用相应的InsertCommand、 UpdateCommand或 DeleteCommand命令语句对数据库进行更新。
(二)、检测DataTable表行状态
示例代码首先使用DataSet对象的HasChanges属性检测数据集是否有修改过的数据,然后调用GetChanges方法获取已更改的数据行并保存到新的数据表中,最后调用OleDbDataAdapter对象的Update方法更新数据库。
'检查数据是否发生更改
If dst.HasChanges Then
Dim dtchanges As DataTable = dst.Tables.Add("dtchanges")
'将更改的行保存到新数据表中
dtchanges = dst.Tables("mytbl").GetChanges
'更新数据库
adapter.Update(dst, "dtchanges")
End If
DataSet对象GetChanges方法还可以带参数,从而可以获取更具体的被修改过的数据行。如:
'获取修改过的行
dtchanges = dst.Tables("mytbl").GetChanges(DataRowState.Modified)
'获取新添加的行
dtchanges = dst.Tables("mytbl").GetChanges(DataRowState.Added)
'获取已删除的行
dtchanges = dst.Tables("mytbl").GetChanges(DataRowState.Deleted)
(三)、接受或拒绝对数据集中数据的更改
对数据集中的数据进更改后,可以使用DataSet对象的AcceptChanges方法接受更改或RejectChanges方法拒绝更改。如:
'检查数据是否发生更改
If dst.HasChanges Then
Dim dtchanges As DataTable = dst.Tables.Add("dtchanges")
'将更改的行保存到新数据表中
dtchanges = dst.Tables("mytbl").GetChanges
'检查发生更改的行中是否包含错误
If dtchanges.HasErrors() Then
'拒绝对数据集所做的更改
dst.RejectChanges()
Else
'更新数据库(注意这句必须在前)
adapter.Update(dst, "dtchanges")
'接受对数据集所做的更改
dst.AcceptChanges()
End If
End If
二、行版本
通过DataRow的RowState属性可以判断哪一行是新插入的,哪一行是被修改过或哪一行已经被删除了,但在调用OleDbDataAdapter对象的Update方法更新数据库时不仅需要获取被修改过后的数据,还需要知道调用OleDbDataAdapter对象Fill方法从数据库填充数据集的原始数据。这就有赖于DataColumn对象的DataRowVersion属性(行版本)。以下提供了对每个 DataRowVersion 枚举值的简短说明。
Current:行的当前值。 如果行的 RowState 属性为 Deleted,则不存在此行版本。
Default:特定行的默认行版本。 Added、Modified 或 Deleted 行的默认行版本是 Current。 Detached 行的默认行版本是 Proposed。
Original:行的原始值。 如果行的 RowState 属性为 Added,则不存在此行版本。
Proposed:行的建议值。 在对行进行编辑操作的过程中,或者对于不属于 DataRowCollection 的行,存在此行版本。
对于新添加的行只有一个版本,而对于修改过的行或已删除的行则需要用主键或唯一约束列的原始值(Original)去跟数据库比对,从而确定要更新或删除的行。
如:
'添加delete命令参数
Dim deletecmd As New OleDbCommand("Delete * FROM stutbl Where ID=?", conn)
deletecmd.Parameters.Add("@ID", OleDbType.BigInt, Nothing, "ID").SourceVersion = DataRowVersion.Original'这里需要用原始值
adapter.DeleteCommand = deletecmd
三、一些理解
1、对 DataSet 或 DataTable 调用AcceptChanges方法时,会移除行状态为 Deleted 的所有行。 剩余行的行状态为 Unchanged,并且 Original 行版本中的值将被 Current 行版本值覆盖。 调用 RejectChanges 时,会移除行状态为 Added 的所有行。 剩余行的行状态为 Unchanged,并且 Current 行版本中的值将被 Original 行版本值覆盖。
2、从数据库中查询并通过OleDbDataAdapter.Fill()方法填充的DataTable,其所有行的状态都为Unchanged,对于在程序中手工构造并添加的数据行,在未接受AcceptChanges()方法前,行状态都为Added。
3、DataRow对象的Delete方法并未真正移除DataRow(除非此行原状态为Added),而只是将行状态变成了Deleted,并“移除”了它的Current版本。这样,当使用OleDbDataAdapter的Update()进行更新时,其内部机制可以根据仍然存在的Original版本数据,为OleDbDeleteCommand填充参数,完成更新数据库的操作。
4、DataRow对象的Remove、Clear方法将彻底地移除行,这种情况下无法生成可执行的OleDbDeleteCommand,也就是说,使用OleDbDataAdapter的Update方法时,并不能像你预想的那样将对应的数据库表数据删除。
5、对 DataSet 、 DataTable或DataRow 调用AcceptChanges方法并不会更改数据库,他只是对数据集中的数据产生作用,与数据库真正相关的是OleDbDataAdapter.Update()方法,它是真正负责执行相关SQL命令的地方。所以你决定要更新数据库必须先使用OleDbDataAdapter.Update()方法,然后才能调用AcceptChanges方法,如果搞反了则不会将更改写回数据库。这两个方法的顺序一定要慎重考虑。