处理脱机数据——用DataSet对象
1 DataSet的特性
DataSet是数据的集合。可以把它看做Excel的工作簿,里面保存了多个工作表(查询结果)。
l DataSet对象与DataReader对象的区别?
DataReader是为速度而创建的,所提供的功能有限。DataReader中的数据是只读的,并且DataReader一旦移动到下一行,就不能返回查看前面的行(单向的)。
DataSet是为处理脱机数据而建的,提供了很多强大的功能。
1.1 处理脱机数据
DataSet中的数据是与数据库断开连接的。(一旦用DataAdapter将查询结果返回DataSet——填充DataSet,DataSet和数据库之间便不再有连接。)
脱机处理数据的好处。最大的好处是不需要始终与数据库连接。
DataSet的脱机数据机制对创建多层应用程序很有用,可以将DataSet的内容从一个组件传递到另一个组件;接收数据的组件可以像XML文档那样处理信息;当该组件是用.NET框架创建之时,可以将信息作为一个DataSet来处理。
1.2 浏览、排序、搜索、过滤
DataSet对象可以在任何时候查看其中的任意行的内容,
DataSet对象还允许修改查看查询结果的方式。(可以将其中的数据根据一列或几列进行排序;根据简单搜索条件查找一行数据;对其中的数据进行过滤)
1.3 处理分级数据
DataSet对象设计上就是用来处理分级数据的。
DataSet对象允许定义其中所存储的多个数据表之间的关系。
1.4 缓存更改
DataSet对象允许缓存一行数据的更改,之后可以使用DataAdapter对象将这些更改提交给数据库;
还可以通过检查DataSet中修改过的行,比较每一行的原始值和当前值,来判断对这些行作出过什么修改(插入、修改、删除)
1.5 XML的完整性
通过创建ADONET的DataSet对象来处理XML,可将DataSet对象的内容保存为XML文档的文件或将XML文档中的数据加载到DataSet对象中;DataSet对象还可以将表、列、约束等架构信息分离到XML架构文件中。
在ADO.NET中,DataSet对象和XML文档几乎是可以互换的。这种二元性允许开发者使用这一最方便的接口(XML开发者可以用DataSet对象处理XML文档;数据库开发者可以像处理DataSet那样处理XML)
1.6 统一功能
ADO的RecordSet对象与ADONET的DataSet对象功能上很相似。
2 使用DataSet对象
DataSet对象和它的子对象:
l DataSet包含DataTable和DataRelation;
l DataTable包含DataRow、DataColumn和约束对象。
2.1 创建数据集对象
DataSet对象的一个可选构造函数中,可以设置数据集的名字属性(DataSetName属性)
l 代码演示
Dim ds as new DataSet()
Dim ds1 as new DataSet(“DataSetName”)
l 所属名字空间
DataSet、DataTable、DataColumn、DataRow、Constraint、DataRelation都驻留在System.Data名字空间中。
2.2 查看调用DataAdapter.Fill()创建的结构
l 准备工作
Dim StrConn,strSql as String
strConn=”Provider=SQLOLEDB;Data Source=(local)"???;Initial Catalog=Northwind;Trusted_Connection=Yes”
strSql=”select CustomerID,CompanyName,ContactName,Phone from Customers”
Dim da as new OleDbDataAdapter(strSql,strConn)
Dim ds as new DataSet()
da.Fill(ds,”Customers”)
在查看查询结果之前,先分析一下DataAdapter创建的用于存储结果的那些结构。
2.2.1 DataTable对象
DataTable是为了更稳定的数据而设计的,可以对DataTable中的数据进行修改、排序以及过滤处理。
DataTable对象有一个Column属性用来返回DataColumn对象的集合,各个DataColumn对应于查询结果中的列。
PS:DAO和ADO的RecordSet对象均包含Fields属性(一个Field对象的集合)
2.2.2 DataColumn对象
DataColumn对象用于定义DataTable的模式。
当使用DataAdapter.Fill()填充DataTable时,DataAdapter对象为查询结果中的每一列创建一个DataColumn对象。(此对象设置了最基本的属性:Name、Ordinal、DataType)
n 代码演示:显示Fill方法创建的DataColumn对象的基本信息
连接数据库部分略
Dim da as new DataAdapter(sSql,sConn)
Dim ds as new DataSet()
da.Fill(ds,”Orders”)
Dim tbl as DataTable=da.Tables(0)
Debug.WriteLine(“Column信息:表名=” & tbl.TableName)
Dim Col as DataColumn
For Each col in tbl.Column
Debug.WriteLine(vbTab & col.ColumnName & “ – “ & col.DataType.toString)
Next Col
2.3 查看DataAdapter返回的数据
ADO.NET到DataTable对象,更符合XML文档,它可树到任意节点。再使用DataTable的情况下,所有的行在任意时间都是可用的。
DataTable类公开了Rows属性,该属性返回DataTable中可用到DataRow对象集合。
2.3.1 DataRow对象
DataRow对象允许查看并修改DataTable中的行。
可以用DataTable.Rows属性将一个DataRow对象指派给DataTable中的特定行。
DataTable.Rows属性返回一个包含DataRow集合的DataRowCollection对象。
n 代码演示:用DataAdapter.Fill方法将查询结果填入DataTable,将第一行分配到DataRow对象并显示其中两列的内容
Dim strConn,strSql as string
strConn=””
strSql=”select OrderID,CustomerID,EmployeeID,OrderDate from Orders”
Dim da as new OleDbAdapter(strSql,strConn)
dim ds as new DataSet()
da.Fill(ds,”Orders”)
Dim tbl as DataTable=da.Tables(0)
Dim row as DataRow=tbl.Row(0)
Console.WriteLine(“OrderID=” & row(“OrderID”) & vbTAB & “CustomerID=” & row(“CustomerID”))
n PS:DataRow对象包含返回指定列内容的参数化Item属性。可以像上述代码那样提供列的名称,或者,提供列中DataTable中的序数位置。
n PS2:在使用DataReader的情况下,使用基于索引到查询比使用基于字符串的查询要更快的返回数据。
2.3.2 检查存储在DataRow中的数据
如果想要辨别一个更一般的历程来显示DataRow中的内容应该怎么做?
假设你是想编写一个接受DataRow对象的,并显示DataRow中列名称和值的过程。
u 若是使用DataReader对象,可以通过检查它的FieldCount属性来确定列的数量,然后用GetName和Item来取得每一列的名称和值。
然而,DataRow中并没有与DataReader对象FieldCount属性相对应的属性。
u DataRow对象公开了Table属性,其中包含了DataRow所在的DataTable。
通过此属性,在返回的DataTable中获取列的总量和各列的名称信息。
n 代码演示:使用DataRow的Table属性来显示DataRow的内容。
Private Sub DisplayRow(ByVal row as DataRow)
Dim tbl as DataTable=row.Table
Dim col as DataColumn
For Each col In tbl.Columns
Console.WriteLine(vbTab & col.ColumnName & “ – “ & row(col))
’从这里看row的item属性可以接受DataColumn类型参数
Next col
End Sub
DataRow对象的Item方法能接受一个DataColumn对象,以便查看特定列的内容。(第三种方法)
通过提供DataColumn来取得行的内容比基于系数的查询性能更好(好6%)
2.3.3 检查DataTable中的DataRow对象
开发者可以像浏览NET框架中的任意集合一样的来回浏览DataTable中的DataRow对象。
可以使用的语句:For循环或For Each循环语句。
n 代码演示:浏览DataTable中的内容(依赖前面代码的DisplayRow过程)
Dim sConn,sSql as String
sConn=””
sSql=”select OrderID,CustomerID,EmployeeID,OrderDate from Orders”
Dim da as New OleDbDataAdapter(sSql,SConn)
Dim ds as new DataSet()
da.Fill(ds.”Orders”)
Dim tbl as DataTable=ds.Table(0)
Dim col as DataRow
Dim i as Integer
For Each row In tbl.Rows
i+=1
console.WriteLine(“Contents of row #” & i)
DisplayRow(row)
Next row
2.4 校验DataSet中的数据
一般,数据库都提供了不同的机制用来确保数据库中的数据的正确性。
例如:Northwind数据库就定义了很多规则和约束:填充Customers表中CustomerID列的字符串不能超过5个字符,且值必须唯一;Orders表自动为每一行生成OrderID,且每行的CustomerID必须对应到Customers表的一个现有项。
也许,在你提交更改之前,需要应用类似的规则校验数据。
但是,由于数据库很可能已经定义了类似的校验规则,这种想法很可能有点多余。但是,在应用程序中添加校验规则可以增强它的性能;另一个好处是减少网络流量和数据库的负载。
ADO.NET的DataSet提供了许多中数据库系统中可用的、相同的数据校验机制,可以将这些校验机制(或者叫约束)分成两类:列级限制以及表级限制。
2.4.1 校验DataColumn的属性(列级限制)
DataColumn对象用于校验数据的属性。
l ReadOnly(只读性)
保证数据正确性的最简单办法(不让用户修改它)。
l AllowDBNull(允许空)
Boolean类型。
l MaxLength(最大长度)
对列中字符串的大小的限制。
l Unique(唯一性)
若值重复,会抛出ConstrainException异常。
2.4.2 DataTable对象的Constrains集合
ADO.NET对象模型包含两个类:UniqueConstrains和ForeignkeyConstraint。它们都是由Constraint类派生而来。
DataTable提供的Constraints属性,可以——添加、修改、查看——位于DataTable上的约束。
2.4.2.1 UniqueConstraints
若数据列的Unique属性被设为True(该列有唯一性),同时,还会中DataTable的约束集合中添加一个UniqueConstraints对象。
PS:在DataColumn中设置Unique,要比,中DataTable的约束集合中创建一个新的UniqueConstraints简单得多。但是,有时候还是需要第二个办法的,比如:需要确定多列合并之后的值是否惟一的时候。
2.4.2.2 Primarykey
主键(Primarykey)是UniqueConstraints的一种特殊类型。
ADONET对象模型中的DataRowCollection对象可以用Find方法来根据主键列的值查找DataTable中的行,好像这样:row=myTable.Rows.Find(“ALFKI”)
注意:数据表可以有多个唯一性约束,但是只能有一个主键。
设置主键,可以用,PrimaryKey属性。
2.4.2.3 ForeignkeyConstraints
外部约束(ForeignkeyConstraints)可以被引入到DataTable中。
例如:前面提到Orders表中CustomerID必须与Customers表中的CustomerID对应。这可以通过创建一个ForeignkeyConstraint对象,并将它添加到DataTable中来对DataSet中的数据进行限制。
PS:通常不必去特意创建ForeignkeyConstaint,因为当DataSet中的两个DataTable之间建立起DataRelation后,会自动创建ForeignkeyConstraint。
注意:ADONET并不知道数据库中保存了哪些数据。因此中DataSet中的列和约束只能中该DataSet中有效。
2.4.3 用DataAdapter.Fill模式来检索模式信息
首先:验证数据是需要时间的。
因此,许多方案都没有对DataSet设置验证属性,除非,有明确的要求。
在内部,DataAdapter的Fill方法,在创建DataTable时,也不会对DataColumn进行验证属性方面的设置,并且,不会将约束添加到DataTable对象的约束集合。
在填充DataTable过程中,可以使用两种办法,通知DataAdapter在数据库中检索其模式信息:
u 通过DataAdapter对象的MissingSchemaAction属性,将它设置为AddWithKey
u 调用DataAdapter对象的FillSchema方法
注意:ADONET有一些功能应该尽量避免中最终应用程序中使用。而,通过DataAdapter对象的上面两种方法获取DataSet的模式信息,就是应该极力避免的行为。
使用DataAdapter获取模式信息,能在设计时节省时间(事实是,VSNET使用DataAdapter中设计时产生了DataSet对象)。
如果只是创建小型示例或者概念示例,都无话可说,的确能减少代码的编写量。但是,一般的情况时,你不需要知道返回的是哪一列,因此,完善的应用程序中,像DataAdapter.FillSchema这样的功能禁止使用。
使用上述两种方法(DataAdapter特性)检索模式信息,会导致:
u 查询它所创建的每一个新DataColumn的名称和数据类型
u DataAdapter中数据库中查询模式信息(查看数据列对象的ReadOnly、AllowDBNull、MaxLength、Unique属性是否都被正确设置)
u DataAdapter也将设置新的DataColumn对象的AutoIncrement属性
DataAdapter将,试图,为DataTable生成一个主键,这一点相当糟糕。原因是:
DataAdapter必须先查询数据库,以便确定查询所引用的表,然后,再一次查询数据库来收集该表主键的信息。若没有,DataAdapter将请求得到该表惟一索引的信息,若该表包含有一个包括两个列的主键,而,所使用的查询却不包含这些列,DataAdapter将不再使用DataTable中的这个主键。
2.5 编写代码成绩DataTable对象
前面学过的内容包括:
ü 使用DataAdapter的Fill和FillSchema方法创建DataTable对象
ü 当想要用列级或者表级限制来验证数据时,应该创建自己的DataTable对象
2.5.1 创建DataTable对象
l 代码举例:
Dim tbl as new DataTable(“TableName”)
Console.WriteLine(tbl.TableName)
2.5.2 将DataTable添加到DataSet对象的Table集合
可以使用DataTableCollection对象的Add方法将一个已存在的DataTable对象添加到现有的DataSet对象当中.代码如下:
Dim ds as new DataSet()
Dim tbl as new DataTable(“Customers”)
ds.Tables.Add(tbl)
代码也可以进行以下简写:
Dim ds as new DataSet()
Dim tbl as DataTable=ds.Tables.Add(“Customers”)
通过查看DataTable对象的DataSet属性,可以确定它是否存在于DataSet中(返回所在的DataSet对象;如果没有则返回Nothing或者Null).这是个只读属性.
DataTable对象只能属于一个DataSet对象(或者没有).因此,如果希望多个DataSet都含有它,必须使用Copy方法或者Clone方法来复制副本.
n Copy方法,能创建一个与原型结构相同,并且包含相同行的新DataTable
n Clone方法能够创建一个与原型结构相同,但是不包含任何行的新DataTable
2.5.3 为DataTable添加列
为了保存查询的结果,DataTable必须包含行.
l [前面提到过]DataAdapter是如何创建新的DataColumn对象
A. 先创建一个DataColumn对象
B. 然后用代码加入到Table对象的列集合中.
C. 代码样例:
Dim da as New DataSet()
Dim tbl as DataTable=da.Tables.Add(“Customers”)
Dim col as DataColumn=tbl.Columns.Add(“CustomerID”)
2.5.4 指定DataColumn的数据类型
创建新DataColumn之后,还需要指定其所包含的数据的类型.
使用DataColumn的DataType属性,可以设置或查看列的数据类型.
但是,如果已经将数据添加到DataTable对象的Rows集合之后,则,DataColumn对象的DataType属性将不可读写.
l DataColumn的数据类型依赖于数据库中该列的数据类型
l 数据库中的数据类型跟DataColumn的数据类型不是一对一的关系.
在Sql Server中,就字符串而言,它的存储方式有:定长或变长。数据可以是单字节或双字节。
然而,对于ADO.NET,以上的类型在DataColumn中都被视为字符串。
DataColumn的DataType属性只处理.NET类型,不处理数据库的数据类型。
DataColumn的DataType属性默认类型是字符串。
DataColumn对象的一个构造函数可以指定其数据类型和名称。
同时,DataColumnCollection.Add方法的一个重载版本也可以运行为新DataColumn对象指定ColumnName和DataType属性。
l 代码示例
Dim ds as new DataSet()
Dim tbl as DataTable=ds.Tables.Add(“Orders”)
Dim col as DataColumn=tbl.Columns.Add(“OrderID”,GetType(Integer))
DataType属性的类型是Type。注意:VBNET和C#使用了不同的函数来生成类型,在VBNET中是GetType函数;在C#中是typeof运算符。
2.5.5 添加主键
注意:用代码设置DataColumn和DataTable的验证特性是应该提倡的;而不要用DataAdapter去查询数据库的约束。
代码设置验证特性,通过数据列DataColumn对象的四大属性:AllowDBNull、ReadOnly、MaxLength、Unique。
l 代码演示:
Dim ds as new DataSet()
Dim tbl as DataTable=ds.Tables.Add(“Customers”)
Dim col as DataColumn=tbl.Columns.Add(“CustomerID”)
col.AllowDBNull=False
col.MaxLength=5
col.Unique=True
col.ReadOnly=False
设置DataTable对象的主键,稍微复杂。其PrimaryKey属性包含一个DataColumn对象的数组,所以不能只是简单的将列的名字设置为主键。
DataTable对象的主键设置分为:用单一列作主键;和使用列组合作主键,两种。
无论哪种,都要先创建一个DataColumn对象数组,再将该数组赋值给DataTable.PrimaryKey属性。
l 代码演示:第一种,单一列主键
Dim ds as new DataSet()
With ds.Tables.Add(“Customers”)
.Columns.Add(“CustomerID”, GetType(String))
.PrimaryKey=New DataColumn(){.Column(“CustomerID”)}
End With
l 第二种,组合列主键
...
With ds.Tables.Add(“Order Details”)
.Column.Add(“OrderID”,GetType(Integer))
.Column.Add(“ProductID”,GetType(Integer))
...
..PrimaryKey=new DataColumn(){.Columns(“OrderID”), .Columns(“ProductID”)}
End With
值得注意的是,代码设置主键之后,ADONET将自动把主键列对象的AllowDBNull设为False
2.5.6 添加其他约束
除了主键约束之外,还有惟一键和外键约束,他们都可以添加到DataTable中。
DataTable对象的约束集合(Constraints)有一个Add的重载方法,它的参数可以用来添加新的主键、惟一键、外键约束。
l 演示代码
n 代码说明
根据CustomerID列将一个惟一键添加到客户表,根据OrderID和ProductID两列将一个惟一键添加到订单明细表,同时将一个外键约束添加到订单明细表(确保OrderID的值与订单表中对应的行匹配)
n 提示
创建约束有两个办法:
第一:直接创建新的约束对象并添加到表对象约束集合。
tbl.Constraints.Add(New UniqueConstraint(...))
第二:使用约束集合对象的Add重载方法,创建新约束并加入集合。
tbl.Contraints.Add(“constraint_name”,ColumnInformation)
推荐:使用 第一种 方法,因为它容易理解,尽管Add方法可以重载。
l 代码演示
Dim ds as New DataSet()
With ds.Tables.Add(“Customers”)
.Columns.Add(“CustomerID”, GetType(Integer))
...
.Constraints.Add(New UniqueConstraint(.Columns(“CustomerID”))’将客户ID添加为惟一键
(.Constrains.Add(“UK_Constomers”, .Columns(“CustomerID”),False))’第二种写法
End with
‘创建订单详细信息的数据表
With ds.Tables.Add(“Order Details”)
.Columns.Add(“OrderID”,GetType(Integer))
.Columns.Add(“ProcedureID”,GetType(Integer))
...
Dim Cols(1) as DataColumn
Cols(0)=.Columns(“OrderID”)
Cols(1)=.Columns(“ProcedureID”)
.Constaints.Add(New UniqueConstraint(Cols))’这里使用组合键设置惟一键
(Constaints.Add(“UK_Order Details”,Cols,False))’第二种办法
‘添加外键
.Constaints.Add(New ForeignKeyConstaint(ds.Tables(“orders”).Columns(“OrderID”),_
.Columns(“OrderID”)))
(.Constaints.Add(“FK_Order Details_Orders”, ds.Tables(“Orders”).Columns(“OrderID”),_
.Columns(“OrderID”))))’第二种写法
End with
2.5.7 处理自增列
ADONET对象模型通过DataColumn对象的三个属性来实现(支持)自动增量列,它们是:
l AutoIncrement
l AutoIncrementSeed
l AutoIncrementStep
将数据列对象DataColumn的AutoIncrement属性设置为True,就可以为数据表的新行生成自动增量值。
l 演示代码
Dim ds as new DataSet()
Dim tbl as DataTable=ds.Tables.Add(“Orders”)
Dim col as DataColumn=tbl.Columns.Add(“OrderID”,GetType(Integer))
Col.AutoIncrement=true
Col.AutoIncrementSeed=-1
Col.AutoIncrementStep=-1
Col.ReadOnly=true
l 建议
无论何时设置AutoIncrement=True之后,都要记住将AutoIncrementSeed和AutoIncrementStep设置为-1。
n 原因如下:
AutoIncrementSeed和AutoIncrementStep属性控制ADONET如何生成新值。
当处理空表时,ADONET会将存储在AutoIncrementSeed的值赋给第一行自动增量列;
接着ADONET将使用AutoIncrementStep属性生成后续的自动增量值。
n 例如:
如果
AutoIncrement=True
AutoIncrementSeed=2
AutoIncrementStep=2
那么ADONET为前5行产生的自增量为:2,4,6,8,10
l 使用DataAdapter.Fill填充DataTable的处理方式稍有不同。
如果将AutoIncrement=True
AutoIncrementSeed=5
AutoIncrementStep=5
当数据表为空时,新增行的自增列值依次为5,10,15,20...
如果用DataAdapter填充数据表,并用DataTable.Rows.Add添加新行,列的新值将取决于从数据库取得的数据。然后,ADONET会根据数据表中出现的最大自增量和AutoIncrementStep属性的值生成后续值。
l 特别注意
ADONET产生自增量最大值的根据是查询的结果(若查询结果只是特定的客户名单,最大值也是根据这个结果产生的)也许数据库已经存在ADONET生成的新值。这一点要特别注意。
在ADONET和NET框架的整体开发中,可以依靠ADONET的自动增量功能,在将行添加到DataTable过程中,让ADONET对行计数,从而达到分页显示DataTable的目的(而不用根据查询规则和行的排序来实现分页)。
l 自动增量的“可为”与“不可为”
n 可为:使用ADONET自动增量特性
n 不可为:将ADONET生成的自动增量值,提交给数据库。(该值仅为占位符,数据库会生成真正的新值)
n 不可为:显示未提交给数据库的,新行的,自动增量值。(数据库可能会根据ADONET产生的增量值生成不同的值)
n 可为:将AutoIncrementSeed和AutoIncrementStep属性值设置为-1,确保生成的占位符的值不会出现在数据库中。
l 代码示例
说明:演示根据简单查询的结果填充DataTable。
填充之前,先添加新的自动增量列。
填充之后,ADONET会为每一行填充生成新的自增值。
Dim strConn As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=。。。"
Dim strSql As String
strSql = "select 客户ID,公司名称,联系人姓名 from 客户"
Dim da As New OleDbDataAdapter(strSql, strConn)
Dim ds As New DataSet()
da.Fill(ds, "客户")
DataGridView1.DataSource = ds.Tables("客户")
da.FillSchema(ds, SchemaType.Source, "客户2")
Dim tbl As DataTable = ds.Tables("客户2")
Dim col As DataColumn = tbl.Columns.Add("行号", GetType(Integer))
With col
.AutoIncrement = True
.AutoIncrementSeed = 1
.AutoIncrementStep = 1
End With
da.Fill(ds, "客户2")
Dim dgv2 As DataGridView
dgv2 = New DataGridView()
Me.Controls.Add(dgv2)
With dgv2
.Top = DataGridView1.Top + DataGridView1.Height + 10
.Left = DataGridView1.Left
.Height = DataGridView1.Height
.Width = DataGridView1.Width
.Show()
End With
Me.Height = dgv2.Bottom + 100
dgv2.DataSource = ds.Tables("客户2")
2.5.8 添加基于表达式的列
数据库管理员通常会避免:在数据库中,包含,由现有数据派生出来的数据。
大多数数据库的查询语言都支持表达式,在查询结果中,可以包含被统计的列。
例如:select OrderID,ProductID,UnitPrice,Quantity,UnitPrice*Quantity as ItemTotal from [Order Details]
如果将查询结果添加到数据表中,就会看到有一列包含了表达式计算结果。
但是,修改数据表中的UnitPrice或Quantity列,不会引起计算列的变化。
ADONET允许创建基于表达式的DataColumn对象,将DataColumn的Expression属性设置为一个表达式。
当查看列内容的时候,ADONET就会计算表达式并返回结果;修改也会反映到计算列中。
l 演示
Dim str_conn As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=。。。"
Dim str_sql As String = "SELECT 产品.产品名称, 订单明细.单价, 订单明细.数量, 订单明细.折扣 FROM (订单明细 INNER JOIN 产品 ON 订单明细.产品ID = 产品.产品ID)"
'第一步我要取表结构,然后添加新列
Dim da As New OleDbDataAdapter(str_sql, str_conn)
Dim ds As New DataSet()
da.FillSchema(ds, SchemaType.Source, "我的查询")
Dim tbl As DataTable = ds.Tables("我的查询")
tbl.Columns.Add("总价", GetType(Decimal), "单价 * 数量")
tbl.Columns.Add("折后价", GetType(Decimal), "总价*(1-折扣)")
da.Fill(ds, "我的查询")
l 拓展
Expression属性支持许多函数。
包括:能引用数据集中的其它数据表中数据的聚合函数。(处理关系数据时,使用聚合函数创建基于表达式的列)
2.5.9 为客户表、订单表、订单详细信息表创建DataTable对象
说明:把DataSet、DataTable、DataColumn结合到一个数据集中。
l 代码示例:
下面的代码创建一个包含3个DataTable的DataSet(包括DataSet对象的每个部分),并用代码设置DataColumn对象的属性(DataType、AllowDBNull、AutoIncrement),创建主键约束和外键约束。
Dim col As DataColumn
Dim fk As ForeignKeyConstraint
'客户表
With ds.Tables.Add("客户")
col = .Columns.Add("客户ID", GetType(String))
col.MaxLength = 5
col = .Columns.Add("公司名称", GetType(String))
col.MaxLength = 40
col = .Columns.Add("联系人", GetType(String))
col.MaxLength = 30
col = .Columns.Add("联系电话", GetType(String))
col.MaxLength = 24
.PrimaryKey = New DataColumn() {.Columns("客户ID")}
End With
'订单表
With ds.Tables.Add("订单")
col = .Columns.Add("订单ID", GetType(Integer))
col.AutoIncrement = True
col.AutoIncrementSeed = -1
col.AutoIncrementStep = -1
col.ReadOnly = True
col = .Columns.Add("客户ID", GetType(String))
col.AllowDBNull = False
col.MaxLength = 5
.Columns.Add("员工ID", GetType(Integer))
.Columns.Add("下定日期", GetType(DateTime))
.PrimaryKey = New DataColumn() {.Columns("订单ID")}
End With
'订单详细信息表
With ds.Tables.Add("订单明细")
.Columns.Add("订单ID", GetType(Integer))
.Columns.Add("产品ID", GetType(Integer))
.Columns.Add("单价", GetType(Decimal))
col = .Columns.Add("数量", GetType(Integer))
col.AllowDBNull = False
col.DefaultValue = "1"
col = .Columns.Add("折扣", GetType(Decimal))
col.DefaultValue = "0"
.Columns.Add("单项总价", GetType(Decimal), "单价 * 数量 * (1- 折扣)")
.PrimaryKey = New DataColumn() {.Columns("订单ID"), .Columns("产品ID")}
End With
'外键约束
fk = New ForeignKeyConstraint(ds.Tables("客户").Columns("客户ID"), ds.Tables("订单").Columns("客户ID"))
ds.Tables("订单").Constraints.Add(fk)
fk = New ForeignKeyConstraint(ds.Tables("订单").Columns("订单ID"), ds.Tables("订单明细").Columns("订单ID"))
ds.Tables("订单明细").Constraints.Add(fk)
2.6 修改DataTable内容
2.6.1 添加新DataRow
这里是指一行行的加载数据
每个DataTable对象都有一个Rows属性。用来返回DataRowCollection对象,此对象包含DataRow对象的集合。(多数集合都可以用Add方法将新对象加入集合)
另外,DataTable对象的NewRow方法,将返回新生成的DataRow对象(其中包含了表中的列信息——字段名、类型、可空等等)。
l 代码:
Dim row As DataRow=ds.Tables(“Customers”).NewRow
row(“”)=”ALFKI”
创建新的DataRow之后,你可以,使用Item属性填充不同的列;还可以用item属性,检查行中列的内容。
Item属性,是DataRow对象的默认属性,因此可以省略掉。(引用DataRow中列的值,需要列名,或者索引)
DataTable的NewRow方法,创建一个新行,但不会将该行添加到DataTable中,因为,该行是空行(若列没有默认值,该列就可能是适当的默认值或Null)。所以,可以在此时,给新行中的列赋值。
假如:客户表的CustomerID列不能接受Null值,也没有默认值。如果不给CustomerID列赋值,但是还要将新的DataRow对象敬爱如到表,就会产生异常。
给新行(DataRow对象)所有必须的列赋值,之后,可以使用DataRow Collection对象的Add方法,将新行加入到表(DataTable对象)。
'客户表
Dim c_id As String = "ALFKI"
Dim row As DataRow = ds.Tables("客户").NewRow
row("客户ID") = c_id
row("公司名称") = "美联邦"
row("联系人") = "奥巴马"
row("联系电话") = "2000302"
ds.Tables("客户").Rows.Add(row)
'订单表
row = ds.Tables("订单").NewRow
row("客户ID") = c_id
row("员工ID") = 821
row("下定日期") = Now
ds.Tables("订单").Rows.Add(row)
Dim o_id As Integer = ds.Tables("订单").Rows(0).Item("订单ID")
'订单明细
row = ds.Tables("订单明细").NewRow
row("订单ID") = o_id
row("产品ID") = 8541
row("单价") = 45.25
ds.Tables("订单明细").Rows.Add(row)
另外:数据表对象(DataTable)提供了另外一种方法——将新行添加到表——LoadDataRow方法。
此方法,第一个参数是一组数值,数组中的项与表中列相对应;
参数AcceptChanges,用来控制新行DataRow的RowState属性值。若设置为False,则新行的RowState属性值为Added(跟使用DataTable.NewRow和Rows.Add组合添加行——效果相同)。
Dim avalues As Object() = {"BFKDL", "民主党", "麦凯恩", "030-0074321"}
ds.Tables("客户").LoadDataRow(avalues, False)
当调用DataAdapter对象的Update方法,将更改提交给数据库时,DataAdapter会检查每个DataRow的RowState,确认如何更新数据库{修改现有行、添加新行、删除现有行}。
若AcceptChanges参数设置为True,则新DataRow的RowState值为Unmodified,表示该行不包含DataAdapter要提交给数据库的挂起更改。
2.6.2 修改当前行
有三种办法可以修改行的内容。
l 最简单办法
一旦有DataRow之后,你就可以使用DataRow对象的Item属性,对列赋值。
Dim ds As DataSet
Dim tbl As DataTable
tbl = DataGridView1.DataSource
ds = tbl.DataSet
Dim rowCustomer As DataRow
rowCustomer = ds.Tables("客户").Rows.Find("ANTON") '记住:Find方法的参数,只能是主键值(可以是组合值)
If rowCustomer Is Nothing Then
Else
rowCustomer("公司名称") = "魔梦"
rowCustomer("联系人姓名") = "我不活了"
End If
l 使用DataRow对象的一对方法:BeginEdit和EndEdit。
Dim r As DataRow
Dim tbl As DataTable
tbl = DataGridView1.DataSource
Dim ds As DataSet
ds = tbl.DataSet
r = ds.tables("客户").rows.find("ANTON")
If (r Is Nothing) Then
Else
r.BeginEdit()
r("公司名称") = "美联储"
r("联系人姓名") = "奥巴马"
r("联系人头衔") = "没好得瑟"
r.EndEdit()
End If
使用BeginEdit和EndEdit组合,可以缓存对行的修改。
调用EndEdit可以保存对行的修改;如果不想保存更改,可以调用CancelEdit取消修改,行会返回到调用BeginEdit时的状态。
n 这两种修改行的方法,不同之处,在于:调用会引发DataTable的一些特殊事件(比如RowChanging、RowChanged、ColumnChanging、ColumnChanged)
方法一,每次修改行中列值,行的内容都改变。每次修改都会激发DataTable对象的事件
方法二,使用BeginEdit可以在调用EndEdit之前阻止事件激活。
l 第三种修改行的方法
使用ItemArray属性(可以检索或修改行的内容)
与Item属性的不同之处在于,ItemArray可以接受对应列的一组值。
Dim tbl As DataTable
tbl = DataGridView1.DataSource
Dim ds As DataSet
ds = tbl.DataSet
Dim r As DataRow
r = ds.Tables("客户").Rows.Find("ALFKI")
Dim values As Object() = {"ALFKI", "太扯淡公司", "克林顿夫妇", "892302323"}
r.ItemArray = values
使用VBNET关键字Nothing或者C#关键字Null,可以跳过不想修改的列。
Dim values As Object() = {Nothing, "太扯淡公司", "克林顿夫妇",Nothing}
l 注意
修改行的内容并不会,自动修改数据库中的相应内容。
对行所做的修改,被视为随后将使用DataAdapter对象提交给数据库的,待定更改。
l 推荐使用BeginEdit和EndEdit方法组合,修改数据表中的行。
因为,它要求所编写的代码结构更好,更容易阅读和维护,而且,出现异常时,可以取消所有的修改
2.6.3 处理DataRow的空值
实际上,判断行的列是否包含空值,很简单。
DataRow对象有IsNull方法,可以查看列是否包含空值。
DataRow.IsNull方法与Item属性使用规则类似,接受参数可以是:列名、列索引值、DataRow对象。
Dim r As Integer
Dim count As Integer = DataGridView1.Rows.Count
Randomize()
r = CInt(count * Rnd())
Dim row As DataRow
row = DataSet1.Tables("客户简表").Rows(r)
row("电话") = DBNull.Value
row("电话") = Nothing ‘这两行都可以设置出空值来,但是不要用空字符串“”
MsgBox("设置了第" & r & "行的电话为空值")
row = DataSet1.Tables("客户简表").Rows(r)
If row.IsNull("电话") Then
MsgBox("果然,第" & r & "行,电话是空值!")
Else
MsgBox("第" & r & "行,电话不是空值啊!")
End If
2.6.4 删除DataRow
删除行,只需要调用DataRow.Delete方法,即可。
然而,删除行不是把它从DataTable中删除掉,而是将行,标记为,挂起删除。
l 为什么呢?
记住,ADONET对象模型中的数据存储对象,是作为数据缓存来执行的。
检索数据库中的数据,修改脱机模式中的数据,然后提交挂起更改——这才是处理的流程。
如果想完全从DataTable中清除行,需要提交更改。
2.6.5 清除DataRow
要从DataTable中清除行,而不是标记为挂起更改,可以用DataRowCollection对象的Remove或者RemoveAt方法。
其中,Remove的参数需要,目标行的引用
RemoveAt的参数,需要目标行的索引编号。
l 代码
Dim row as DataRow
Row =ds.Tables(“Customers”).Rows.Find(“ALFKI”) ‘此处要注意,Find方法只能在主键中查找
Ds.Tables(“Customers”).Rows.Remove(row)
DataSet和DataTable两个对象都包含Clear方法,此方法用来清除所有的DataRow对象,而保留其结构。
2.6.6 使用DataRow.RowState属性
提交更改,到数据库,可以使用:
一、直接执行Sql语句的办法
二、使用存储过程
但是,修改行的逻辑与插入或者删除行的逻辑是不一样的,ADONET提交更改需要知道DataRow所作修改的类型,此信息被存储在DataRow对象的RowState属性中,该属性使用DataRowState类型枚举值来记录,行修改的类型。如下
常数 |
值 |
描述 |
Unchanged |
2 |
该行不包含任何挂起更改 |
Detached |
1 |
该行不是DataTable的成员 |
Added |
4 |
该行已被添加到DataTable中,但还不存在于数据库 |
Modified |
16 |
该行包含挂起更改 |
Deleted |
8 |
该行包含挂起删除 |
通常,RowState属性返回的都是枚举类型的一个值。而不是,你想当然的值组合。
下面是一些方案和结果
示例 |
DataRowState |
新创建,但却分离的行: row=tbl.NewRow row(“ColX”)=”InitVal” |
Detached |
将新行添加到数据表 Tbl.Rows.Add(row) |
Added |
新获取的行: Row=tbl.Rows(0)或者row=tbl.Rows(“Colx”) |
Unchanged |
编辑以后: Row.BeginEdit() Row(“Colx”)=”NewVal” Row.EndEdit() |
Modified |
删除行之后: Row.Delete() |
Deleted |
2.6.7 检查DataRow中的挂起更改
使用RowState属性可以,浏览DataTable的内容并,找到修改的行。
使用DataRow的Item属性,可以查看列的内容。
Item属性的第二个可以接受DataRowVersion类型的枚举值,含义如下
常量 |
值 |
描述 |
Current |
512 |
列当前值 |
Original |
256 |
列-原始值 |
Proposed |
1024 |
列的建议值(注意:只有在使用BeginEdit方法编辑行时才有效) |
Default |
1536 |
默认操作 |
一般来说,DataRow对象有行的当前值和原始值——这两个版本。
更新行之后,可以查看列的当前值和列的原始值。
l 代码演示:
Dim row As DataRow
row = ds.Tables("客户").Rows.Find("ALFKI")
row("公司名称") = "锐思控股"
ListBox1.Items.Add("列的当前值为- " & row("公司名称", DataRowVersion.Current))
ListBox1.Items.Add("列的原始值为- " & row("公司名称", DataRowVersion.Original))
使用BeginEdit和EndEdit时,你可以取回DataRowVersion的建议值——Proposed。
DataRow的各种状态和使用不同的DataRowVersion取回的Item值,列表如下:
示例 |
当前 |
原始 |
建议 |
默认 |
新建但独立的行: Row=tbl.NewRow Row(“Col1”)=”Val1” |
Val1 |
[Exception] |
[Exception] |
NewValue |
将新行加入表: Tbl.Rows.Add(row) |
Val1 |
[Exception] |
[Exception] |
NewValue |
取回的新行: Row=tbl.Rows(0) |
Retrieved_Value 寻回的值 |
Retrieved Value |
[Exception] |
Retrieved Value |
第一次编辑过程中: Row.BeginEdit() Row(“Col1”)=”NewVal” |
Retrieved_Value 寻回的值 |
Retrieved_Value 寻回的值 |
NewVal |
NewVal |
第一次编辑以后: Row.EndEdit() |
NewVal |
Retrieved-Value |
[Exception] |
NewVal |
第二次编辑中: Row.BeginEdit() Row(“Vol1”)=”NewVal2” |
NewVal2 |
Retrieved Value |
NewVal2 |
NewVal2 |
取消编辑之后: Row.BeginEdit() Row(“Col1”)=”valCancel” Row.CancelEdit() |
NewVal2 |
Retrieved value |
[Exception] |
NewVal2 |
删除行之后: Row.Delete() |
[Exception] |
Retrieved Value |
[Exception] |
[Exception] |
l 执行成功的操作,会影响【当前值】,但不会影响【原始值】
l 调用CancelEdit方法会将当前值重置为调用BeginEdit方法之前的状态,但是,不一定与原始值相同。
l 删除行后,如果试图查看其当前值,会产生异常,但是,仍然可以访问它的初始值。
Ø 关于Default
DataRow的Item属性中第二个参数使用Default,不会返回列的默认值(那是数据行的DefaultValue属性的功能)
这个Default实际上表示的是,DataRow对象的Item属性中参数的默认值。
Ø 关于这里的“当前”的理解
如果不是处于“编辑行”的状态,调用Item属性,实际上省略的可选参数(第二参数)是DataRowVersion.Current;
如果处于“编辑行“的状态,调用Item属性,省略的可选参数是“建议”Proposed