深入.NET DataTable

http://www.cnblogs.com/kissknife/archive/2008/11/17/1335271.html

深入.NET DataTable

1ADO.NET相关对象一句话介绍
1)DataAdapter
DataAdapter 实际是一个 SQL 语句集合,因为对 Database 的操作最终需要归结到 SQL 语句。
2)Dataset
DataSet 可以理解成若干 DataTable 的集合, DataSet 在内存里面维护一个表集合包括表间关系。对于 .NET Framework 2.0 之前的版本, DataSet ADO.NET 中拥有至关重要的作用,但在其后的版本中,由于 DataTable 类的完备 ( 例如与 XML 相关的几个方法以及 Merge 方法 ) ,其作用稍有削弱,甚至于有些情况下你去初始化一个 DataSet 对象本身就是多余的。
3)DataView
与数据库中的视图在概念上是类似的。 DataView 本身并不真正包含数据行,而只是包含指向源 DataTable 中数据行的引用,这一点你可以通过 object.ReferenceEquals() 方法来验证
4)DataTable
ADO.NET 的核心对象。它是位于内存中的一张表,是你执行 SQL 查询之后的结果集,可以形象地把它理解为一张包含若干行若干列的表格。
 
2、如何更新数据到Database
从本质上来说,你对 Database 操作总是归结到 SQL 语句,但是从表面上我们可以作一点区分,
1) 直接使用 SQL 命令
.NET 中,最常见的是拼接 SQL 字符串,使用 Command 对象来执行此命令以达到操作 Database 的目的,例如, 
Code
这是一种最直接浅显的方式,因为 SQL 语句就在你眼前,反过来说,这需要你对 SQL 命令有一定的了解。

2)
使用 DataAdapter.Update()
另外一种方式,是使用 DataAdapter.Update() 方法,这并不是说我们不需要 SQL 语句了,只是 SQL 语句拼接的工作已经交给了 DataAdapter (实际上是交给了 CommandBuilder )来完成(以参数的形式),例如,  
Code
在这里,你看不到 SQL 语句,因为在你初始化 SqlCommandBuilder 的过程中,将自动根据表结构(基于你的 Select 语句 )构造 insert,update,delete 语句。对于上面的代码,你可以获得 SQL 语句内容,
DELETE FROM [table1] WHERE (([fname] = @p1) AND ((@p2 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p3)))
而执行时候,会传入相应的参数值,
exec sp_executesql N'DELETE FROM [table1] WHERE (([fname] = @p1) AND ((@p2 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p3)))',N'@p1 varchar(1),@p2 int,@p3 int',@p1='a',@p2=0,@p3=100

xec sp_executesql N'DELETE FROM [table1] WHERE (([fname] = @p1) AND ((@p2 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p3)))',N'@p1 varchar(1),@p2 int,@p3 int',@p1='b',@p2=1,@p3=NULL
由于表中只有两个列,列 fname 为主键列, fvalue 列可空,至于为什么会出现三个参数,看看上面的 SQL 你就会明白了。
以下则分别是 update 语句、 insert 语句,
UPDATE [table1] SET [fname] = @p1, [fvalue] = @p2 WHERE (([fname] = @p3) AND ((@p4 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p5)))
INSERT INTO [table1] ([fname], [fvalue]) VALUES (@p1, @p2)
另外,上述 C# 代码中的 dt.Rows[0].Delete() 行写在这里只是示例作用,实际的系统中,你可能会有一个叫“ Delete ”的按钮,这样你可以在按钮的事件中执行 Delete() 操作,然后叫某个叫“ Save ”的按钮里写上 Update() ,这很常见,不多说了。
再另外,由于这些语句的构造过程中依赖于你的 Select 语句,所以你的 Select 语句中必须包含主键列,否则无法正常生成其它 SQL 命令。
以下我们的讨论,将主要针对第二种方式,即使用 Update() 进行数据更新过程中涉及的各种问题。
 
3、行状态
为了后续的数据操作, DataTable 中引入了一个“行状态”的概念(事实上该属性属于 DataRow 类)。每一个 DataRow 都有一个状态标志,你可以通过 DataTable.Rows[i].RowState 查看,对 DataRow 的不同操作将导致该行处于不同的状态,同时,不同的状态又导致保存数据时的不同行为。参见下图,
深入.NET DataTable_第1张图片 

1)
初始状态差异
从数据库中查询并通过 DataAdapter.Fill() 方法填充的 DataTable ,其所有行的状态初始都为 Unchanged (我们可以认为在 Fill() 方法的内部调用了 AcceptChanges() 方法),然而对于在程序中手工构造并添加的数据行,在未接受 AcceptChanges() 方法前,都为 Added (行状态的不同在 DataTable 中是一个比较隐蔽的但又需要十分关注的问题,后续会有相应的说明),参见以下代码。 
Code

  2) 理解 Delete()
此方法并未真正移除 DataRow (除非此行原状态为 Added ),而只是将 RowState 状态变成了 Deleted (当然这会导致你无法使用正常的索引方式访问此行的数据)。对于 Added 状态的行执行 Delete() 操作,将导致 DataTable 行数减少,这点需要注意,因为它可能导致你在使用 for 循环遍历时出现索引越界异常。 
Code

3)Exception:Deleted row information cannot be accessed through the row.  
Code

4) 理解 AcceptChanges()
此方法容易给人误解,以为在调用它之后对 DataTable 所做的所有更改将会被提交到 Database 。事实上,此方法跟 Database 没有直接的关系(注意),它只直接影响各 DataRow RowState (具体地说来是将所有状态为 Deleted 的行真正移除,所有状态为 Added Modified 的行都变成 Unchanged )。与 Database 有直接相关的是 DataAdapter.Update() 方法,它是真正负责执行相关 SQL 命令的地方。
但是,从另一方面来说,没有直接的影响,言外之意就是有间接的影响,由于它影响了所有 DataRow RowState ,而 DataAdapter.Update() 方法在执行 SQL 命令时必须依据 RowState 以确定使用 insert update 、或 delete 命令。举个例子,如果你在 DataAdapter.Update() 调用之前执行 AcceptChanges() 方法,这将阻止所有对 Database 的更改,因此对这两个方法调用的顺序应有充分的考虑。
另外, DataSet DataTable DataRow 都有 AcceptChanges() 方法,这些方法除了影响的范围大小不同之外,没有本质的区别。
 
5)DataRowState Update()
不同的数据行状态,将导致最终 DataAdapter.Update() 出现不同的行为,例如对于 Added 状态的行,将导致 insert 操作、 Modified 状态将导致 update 操作、 Deleted 状态将导致 delete 操作。
 
6) 使用 DataRowState
除了 Update() 方法内部使用 DataRowState 外,在我们自己写的代码中,也可以将它与 GetChanges() 方法配合使用,以获取 DataTable 的当前变化,参见以下 代码,在你获得所有发生更新的行后,实际上你可以自己构造 Update SQL 命令,而不使用 CommandBuilder ,当然这需要用到稍后会提到的 DataRowVersion   
Code

7)状态Detached
除了上图中给出的几种行状态外,还有一种特殊的状态 Detached ,这种状态表示已初始化但未添加到 DataTable 中的数据行,此状态我们不必太关心。参见,    
Code
 
4、行状态、行版本、行数据版本
行版本( DataRowVersion )描述数据行的版本;
行数据版本( DataViewRowState )描述数据行中数据的版本。
这两个概念令人困惑,我认为可以仅仅从用法上对它们进行了解,毕竟我们使用它们的机会并非很大。 深入.NET DataTable_第2张图片 

1)
使用 DataRowVersion
关于 DataRowVersion ,以状态为 Modified 的行为例,它包含两个 DataRowVersion (即存储了两行数据): Current,Original ,分别存储该行修改后与修改前的数据,也就是说,行版本实际可以帮助 RejectChanges() 等方法 实现一个类似于“回滚”的功能。  
Code
同理你可以借助
DataRowVersion来访问Deleted的数据,前面我们提到了对于Deleted的数据,使用 dt.Rows[0]["fvalue"] 访问 将引发异常,可以使用
dt.Rows[0]["fvalue", DataRowVersion.Original]

2) DataRowVersion
Update()
现在我们回想一下,当我们使用CommandBuilder构造完Update,Insert,Delete命令之后,那些SQL命令中的参数怎么办?我们知道在SQL命令执行之前,我们必须为所有输入参数指定参数值,那么Update()方法内部是如何工作的?这就有赖于DataRowVersion了。
 
我们可以简单看一下Update()方法使用过程中涉及的相关.NET源码,
System.Data.Common.DbDataAdapter
protected virtual int Update(DataRow[] dataRows, DataTableMapping tableMapping);
Update()方法中,调用了ParameterInput(),下面是该方法的摘要  
Code
ParameterInput() 方法中,调用了 GetParameterSourceVersion() 方法  
Code
以行被更新的情况为例,在为参数的赋值的过程中,系统会将相应要更新的 DataRow 一并传入,同时对于 Update 语句,
UPDATE [table1] SET [fname] = @p1, [fvalue] = @p2 WHERE (([fname] = @p3) AND ((@p4 = 1 AND [fvalue] IS NULL) OR ([fvalue] = @p5)))
我们要了解的一点是, 5 个参数中 @p1,@p2 是一类, @p3, @p5 是一类,它们的区别在于,前一类的 SourceVersion Current ,而后一类的 SourceVersion Original ,这在上述的 GetParameterSourceVersion() 方法中被用到,所以!!,针对传入的需要更新的 DataRow Update() 方法内部将使用当前值(即修改后的值)填充 @p1,@p2 ,而使用原始值(即修改前的值)填充 @p3, @p5 Insert,delete 同理。

3)理解DataRowVersion.Default
对于 Added Modified Deleted 状态的行,其 Default 版本实际是 Current 版本,对于 Unchanged 则无甚区别。
 
4) 使用 DataViewRowState
(1) 配合 DataTable.Select()  
Code
 

结果输出:
-----------------------------------------------
Added:
li: 100
-----------------------------------------------
CurrentRows:
zhao: 100
qian: 101
li: 100
-----------------------------------------------
Deleted:
sun: 100
-----------------------------------------------
ModifiedCurrent:
qian: 101
-----------------------------------------------
ModifiedOriginal:
qian: 101
-----------------------------------------------
OriginalRows:
zhao: 100
qian: 101
sun: 100
-----------------------------------------------
Unchanged:
zhao: 100
 
(2) 配合 DataView.RowFilter   
Code
//
-----------------------------------------------  
Added & ModifiedCurrent:

qian: 101
li: 100
-----------------------------------------------
 
5)DataViewRowState 中的“复合版本”
DataViewRowState 包含多个枚举成员,我可以给出每个枚举成员对应的 int 值,
Added                     4
CurrentRow              22
Deleted                   8
ModifiedCurrent        16
ModifiedOriginal        32
None                       0
OriginalRow              42
Unchanged              2
你可以发现,其中的两个状态 CurrentRow OriginalRow 实际是经由其它几种状态二进制或运算的结果,
CurrentRow=Added|ModifiedCurrent|Unchanged
OriginalRow=Deleted|ModifiedOriginal|Unchanged
 
5、了解其它几个方法
1)Delete()
Remove() Clear()
DataRow.Delete()
DataRowCollection.Remove()
DataTable.Clear() DataSet.Clear()
正如前面所述,对于 DataRow Delete() 方法,其内部的处理并未真正删除此行,而只是将行标识为 Deleted ,并“移除”了它的 Current 版本。这样,当使用 DataAdapter Update() 进行更新时,其内部机制可以根据仍然存在的 Original 版本数据,为 DeleteCommand 填充参数,完成更新数据库的操作。
Clear() 方法则完全删除了堆上的数据行对象,并且将对数据引用置空(这点可以参见 Clear() 方法的反编译代码),这种情况下无法生成可执行的 DeleteCommand ,这就是说,当你用 Clear() 方法“清空” DataTable 后,使用 Update() 方法并不能像你预想的一样将对应的数据库表数据删除。
另外,需要注意一点是 Delete() 并不导致数据行减少(除非原行是 Added 状态),当然,如果是对 Added 状态的行执行 Delete() ,则导致行数减少。当你使用 for 循环时,这可能会造成问题。
另外,我们还有一个方法: DataRowCollection.Remove() ,其作用类似于 Clear() ,是彻底地移除行,假设你是使用 DataAdapter.Update() 方法更新 Database ,那么你将没有机会将你的删除操作同步到 Database 中。

2)Copy() Clone()
.NET 中有两类拷贝,浅拷贝 (Shadow copy) 、深拷贝 (Deep copy) ,对于大多数我们所见的类(比如常见的集合类等等),没有深拷贝方法,大多数会有一个浅拷贝方法 Clone() 我唯一所见的一个深拷贝方法是 DataTable.Copy() ,同时, DataTable.Clone() 方法也比较特殊,因为它并非是浅拷贝方法,而是拷贝 DataTable 的结构(不包含数据)。
顺便提一下深、浅拷贝的区别,浅拷贝创建原对象类型的一个新实例,复制原对象的所有值类型成员,对于引用类型成员,则只复制该值的指针。深拷贝则复制原对象的所有成员,对于引用类型成员,亦复制其所指的堆上的对象。   
Code
 
3)Select() Compute()
这两个方法在很多情况下都有助于你简化代码,避免每一次使用循环遍历 DataTable ,参见以下,
对于这两个方法中可用的表达式,参见,
http://msdn.microsoft.com/en-us/library/system.data.datacolumn.expression.aspx 
Code

----------------------------------
p7      7
p8      8
p9      9
p10     10
----------------------------------
 
----------------------------------
p7      7
p8      8
----------------------------------
 
----------------------------------
p1      1
p10     10
----------------------------------
 
----------------------------------
p1      1
p3      3
----------------------------------
 
----------------------------------
p10     10
----------------------------------
 
----------------------------------
p2      2
p4      4
p6      6
p8      8
p10     10
----------------------------------
 
----------------------------------
p10     10
----------------------------------
 

 

你可能感兴趣的:(Datatable)