1. DataTable类
DataTable类即是存在于内存中的表格式数据,包括行、列及约束,其中行主要用于存储数据,而列和约束共同组成DataTable的Schema(架构)。
2. 创建主键组合列
主键可以一列构成,也可由多列构成,统一起见,ADO.NET在创建主键时,都使用列数组,例:
cars.PrimaryKey = new DataColumn[] { vin };
3. 使用自增长列做为主键
在应用程序中设置自增长列的方法如下:
1) 将列的AutoIncrement属性设为true
2) 将列的AutoIncrementSeed设为-1
3) 将列的AutoIncrementStep设为-1
第二、第三步设为-1的原因为,在多个客户端同时生成数据传回数据库时,极可能出现数据冲突,抛出异常,如将Seed和Step设为-1,则应用程序生成的编号永远为负数,避免了与数据库编号相冲突。
4. 向DataTable添加数据(DataRow)的方法
DataTable拥有一个Rows属性,返回一个DataRowCollection对象。向DataTable添加数据,即是向DataRowCollection中添加DataRow。添加的方法大致有三种:
1) DataRow newCar = cars.NewRow();
newCar["Vin"] = "123456789ABCD"
cars.Rows.Add(newCar);
2) cars.Rows.Add("123456789ABCD");
3) cars.LoadDataRow(new object[] {"123456789ABCD"}, LoadOption.OverwriteChanges);
其中第三种方法,使用DataTable.LoadDataRow()方法,是“查找并更新行”的意思,如果没有查找到相关行,则创建这个行。
5. 数据行版本(DataRowVersion)的4个枚举类型值
为了向数据使用者提供数据行在不同状态下的值,数据行(Data Row)拥有3种基本版本状态,即Original,Current和Proposed,当然还有第4种,Default。当数据行刚被载入时,它只有一个版本状态,即Current(并非Original,Original即意味着有改更发生),此时的Default为Current;当执行了BeginEdit方法后,数据行进入更改模式,更改后的数据作为一个新的实例存在,其版本状态称作Proposed,此时的Default为Proposed;执行了EndEdit方法后,Proposed变为Current,而原先的Current变为Original,Proposed就不存在了,此时的Default为Current。再次调用BeginEdit方法后,Current变为Proposed,Default为Proposed; 再调用EndEdit方法,Proposed变为Current,Proposed不存在了,但要注意,Original这次没有变化,还是当初载入的那一个,此时的Default为Current。即Original保留上一次载入或是最近一次AcceptChanges方法调用后的那个值。
另需注意,DataRow的DataRowVersion属性值(通过HasVersion(Version)方法获得)与其DataRowState属性值相关联,如Original只在DataRow的状态变为Modified, Unchanged或Deleted时有意义,而在Added状态下,会抛出异常(DataRow.HasVersion(Original))。
6. AcceptChanges和RejectChanges方法
这两个方法同时存在于(可用于)DataSet, DataTable和DataRow对象,明显的,在DataSet上执行方法时,其包含的所有DataTable和DataRow均会受到影响,反之,如果只在某个特定的DataRow上执行此方法,则其父辈的DataTable和DataSet不受任何影响。
一般情况下,载入数据后,DataRow的状态(State)为Added,此时调用AcceptChanges或RejectChanges方法,则DataRow的状态变为Unchanged,再改变一下DataRow的值,其状态又变为Changed。调用一下AcceptChanges,则产生一个Original版本,当然如果调用RejectChanges,则不会产生Original版本,只有一个Current版本。
在完成数据编辑后,可以调用DataTable的GetChanges方法,返回一个DataTable,其中的内容仅为自上次AcceptChanges方法调用后变动过的DataRow行,这样回传至远程数据库时,数据量就要小得多。
需注意:
1) 当数据与数据库成功同步后,一定要调用AcceptChanges方法将DataRow的值统一设置为Unchanged,以与远程数据库同步。
2) 当调用RejectChanges时,Original里的值要拷贝至Current,以还原Load时的初始状态或是上一次调用AcceptChanges后的状态。
7. 使用SetAdded或SetModified将行状态强行设置为Added或Modified
在某些情况下,需要将行(DataRow)的状态改为Added或Modified以便DataAdapter有效识别并上传至远程服务器。需要注意的是,这两个方法均只能使用在DataRow对象上,且需保证DataRow的状态为UnChanged,否则会出错。使用SetAdded方法时,会丢弃DataRow的Original状态值,因为一个具有Added状态的行,是不可能有Orginal状态值的。
8. 删除和恢复
使用行(DataRow)的Delete方法可将需删除的行标注出来(但并未真正删除),些时此行的Current和Proposed状态值被丢弃,如果此时调用调用RejectChanges方法,则Original状态值被拷贝至Current,其状态变为Unchanged,但自load或AcceptChanges调用以来的所有更改均舍弃,若调用AcceptChanges方法,行被真正删除,再引用此行会抛出异常。
9. DataTable的复制和克隆方法
复制和克隆在中英文中并没有什么区别,但作为DataTable的两个方法,二者区别明显。Copy方法用于复制表格的结构和数据,而Clone方法仅复制表格的结构,而不复制数据。
10. 行版本和行状态的区别
DataRow有版本(Data Version)和状态(Data State)的区别,其中版本有4个,即Original, Current, Proposed, Default;而状态有5个,即Detached, Added, Deleted, Unchanged, Modified。版本和状态是同时存在的,但其任意组合并不是一定存在。如行被Deleted以后,其Current和Proposed数据均被丢弃,只剩下Original供还原时使用。
11. DataViewRowState枚举对象
这个枚举对象听名字好像只与状态相关,其实是版本与状态的结合,其包含有8个状态:
Added, CurrentRows(相当于Added|Unchanged|ModifiedCurrent), Deleted, ModifiedCurrent, ModifiedOriginal, None, OriginalRows和Unchanged
12. DataView的输出问题
先看代码:
static void PrintView(DataView dv)
{
StringBuilder buffer = new StringBuilder();
foreach (DataColumn dc in dv.Table.Columns)
buffer.AppendFormat("{0, 15}", dc.ColumnName);
foreach (DataRowView dr in dv)
{
buffer.Append("\n");
foreach (DataColumn dc in dv.Table.Columns)
buffer.AppendFormat("{0, 15}", dr.Row[dc]);
}
Console.WriteLine(buffer.ToString());
}
红色标注部分为重点。可以看出,对列的读取,可取视图的表格属性,再取其列;但对于行,如果也用其表格属性再取其行,则其所做的筛选信息将全部丢失,所以只能使用DataRowView对象(可以看出,其实DataView就是DataRowView的集合)。
当然,我们也可以将DataView转换为DataTable,然后再用我们熟悉的方法输出,但转换产生的开销比较大,用多了自然会影响效率。
13. DataSet对象
DataSet可看成是内存中的数据库,但其本身与数据库的差别还是很大的,这是后话。DataSet包含一组DataTable对象和一组DataRalation对象,DataTable对象本身可包含唯一键及外键,用以保证数据完整性。
我们可以使用手工编程的方法创建DataSet对象,也可以使用XSD(XML schema Definition)文件创建强类型的DataSet对象,后者更简单,更常用。
14. DataRelation对象
DataRelation对象用以将DataSet对象内的多个DataTable对象关联起来,其作用相当于数据库中的表间关系。在创建DataRelation对象时,我们可以指定主键及外键约束,如在下面的语句中:
ds.Relations.Add("vendor_parts", vendors.Columns[0], parts.Columns[2]);
ds.Relations.Add("vendor_parts", vendors.Columns[0], parts.Columns[2], true);
两条语句的作用一样,只是第二条语句指定了createConstraints这个布尔值,即是否在父表中创建主键约束,在子表中创建外键约束,默认为true,这也符合我们一般的需求。
另一个有用的默认值为创建级联完整性约束(Cascade),默认为Rule.Cascade。
15. 合成DataSet数据
DataSet数据合成在程序中非常常见。
16. Path类
Path类存在于System.IO命名空间,用于处理文件及文件夹路径操作,拥有跨平台的特点和好处,其常用方法为:Path.Combine(Param string[] paths),用作将多个paths联成一个整体的Path。
17. 序列化 (Serialization)和逆序列化(Deserialization)
序列化即是将DataSet中的数据保存为xml或是bin(流)文件存放在本地,而逆序列化正好相反,从本地的xml或是bin(流)文件中读入构架和(或)数据,生成DataSet。以xml文件为例,序列化使用DataSet.WriteXml()方法,逆序列化使用DataSet.ReadXml()方法。
18. 序列化为xml文件
上面已经提到过,序列化为xml文件时,使用DataSet的WriteXml()方法,此方法需2个参数,一个是要保存的文件名,要全路径名,另一个是序列化类型尾数,是一个枚举值,有3个,分别为WriteSchema(架构+数据),IgnoreSchema(仅数据)和DiffGram(含Original和Current值)。要修改xml文件中的显示方式和内容,可做相应个性,如要改变表格名称,可修改DataTable的TableName属性;要修改各列在xml中的显示方式,可修改各列的ColumnMapping属性(ColumnMapping属性仅对生成的XML文件起作用)。其中ColumnMapping的值有4个,分别为Attribute, Element, Hidden, SimpleContent。其中Attribute和Element与Xml语法相关。
19. 将更改过的DataSet保存(序列化)为xml文件(使用XmlWriteMode.DiffGram)
格式如下:
cars.WriteXml(desktopFileName("cars.xml"), XmlWriteMode.DiffGram);
这里的DiffGram保存了所有的DataRowVersion信息。利用此格式的XML文档保存和恢复DataSet将不会有信息丢失。
20. 从文件创建DataSet(Deserialize,逆序列化)
需要特别注意的是,如果没有提供架构信息,则所有的数据都会被当成是字符数据,所以在从XML读取数据前,一定要先载入架构信息。
示例代码如下:
DataSet ds = new DataSet();
ds.ReadXmlSchema(desktopFileName("cars.xsd"));
ds.ReadXml(desktopFileName("cars.xml"), XmlReadMode.IgnoreSchema);
其中的XmlReadMode枚举值有:Auto(自动选择合适的值),DiffGram(见19点),Fragment(作为片段读取),IgnorSchema(不读取架构信息),InferSchema(自动推断架构信息),InferTypedSchema(自动推断,但新加项不再为string),ReadSchema(加载xml文档中的架构信息,如果没有架构信息,则抛出异常)
21. 使用DataTableReader类循环读取数据
DataTableReader类用于迭代一个或多个数据表(DataTable)中的数据行(DataRow),返回的行数据只读、只进(Read Only & Forward Only),换言之,在迭代过程中,你可以往DataTable中添加或删除数据,如果修改的数据在指针(Position Cursor)以前,则修改的数据就不管了,但如果修改的数据在指针以后,则修改的数据要反映出来。当指针到达行末尾时,Read方法返回Null。语法如下:
DataTableReader rd = cars.CreateDataReader();
while(rd.Read())
{
do something;
}
rd.NextResult();
while(rd.Read())
{
do something;
}