内存中的数据库--DataSet类
DataSet类是数据的脱机容器。数据集由一组数据表组成,每个表都有一些数据列和数据行
DataSet对象中包含:数据表和数据关系
实例化DataSet:
DataSet ds = new DataSet();
//这只是生成了一个空的数据集,里面不包含任何表。
DataSet属性:
Tables:(DataTableCollection)表集合,用来管理其中的各子表。
Relations:(DataRelationCollection)关系集合,用来管理其中的表与表之间的关系。
DataSet方法
WriteXml():将DataSet的内容写入的内容写入XML文件中。
ReadXml():将xml文件中的内容写到文件中的内容写到DataSet中。
内存中的表--数据表(车延禄)
数据表非常类似于物理数据库表,它由列组成,可能包含0行或多行数据。数据表也可以定义主键码(可以是一个列或多个列),列上也可以包含约束。
实例化DataTable:
DataTable dt = new DataTable();
//这里的dt也只是一个空对象,其中没有任何列和行
ds.Tables.Add(dt);
//将数据表加入到数据集中
DataTable属性:
Rows:(DataRowCollection)管理表中所有的行
Columns:(DataColumnCollection)管理表中所有的列
Constraints:(ConstraintCollection)管理表中所有的约束集合
TableName:(String)数据表在数据集中的标识名
PrimaryKey:(DataColumn[])表的主键列的数组
DefaultView:(DataView)表的默认视图
DataTable方法:
NewRow():(DataRow)根据表的结构生成一个新行对象。
DataColumn数据列:
数据表中的列
实例化:
DataColumn dc = new DataColumn();
或
DataColumn dc = new DataColumn(string,Type);
dt.Columns.Add(dc);//将列对象加入到表的列集合中
DataColumn属性:
AllowDBNull:(bool)是否允许该列为空
AutoIncrement:(bool)该列是否是自增长表
ColumnName:(string)当前列的列名
DataType:(Type)当前列的数据类型
MaxLength:(int)当前列的宽度
ReadOnly:(bool)当前列是否是只读列(只能插入删除,但不能修改)
Unique:(bool)当前列是否是唯一列
DefaultValue:(object)当前列的默认值
(车延禄)
DataRow数据行:
表中的数据行,创建表的时候一定要先创建列再添加行
DataRow 对象一般是用DataTable对象的NewRow方法来生成新行
方法:
Delete():删除当前行
GetChildRows():获取相关联子表中的对应的行(需要事先在Dataset中建立起表的关联)
GetParentRow():获取相关联父表中对应的行(需要事先在Dataset中建立起表的关联)
整个数据行有一个状态标志RowState,RowState标志跟踪对DataTable所作的所有改变,当数据与数据库保持一致时,行的状态标志用于确定应执行什么SQL操作。
Added--把新数据行添加到DataTable的Rows集合中。在客户机中创建的所有行都设置为这个值,最终在与数据库保持一致时,会使用SQL INSERT语句
Deleted--通过DataRow.Delete()方法把DataTable中的数据行标记为删除。该行仍存在DataTable中,但在屏幕上看不到它(除非显式设置DataView)。DataView在下一章讨论。在DataTable 中标记为已删除的Rows将在与数据库保持一致时从数据库中删除
Detached--数据行在创建后立即显示为这个状态,调用DataRow.Remove()也可以返回这个状态。分立的行不是任何DataTable的一部分,因此处于这种状态的行不能使用任何SQL语句
Modified--如果列中的值发生了改变,就会修改一行数据
Unchanged--自从最后一次调用AcceptChanges以来,数据行都没有发生改变
手动创建DataSet的示例:
DataTable dt = new DataTable(“login”); //生成新的数据表
//定义一个新列“ids” ,类型为整型
DataColumn dc0 = new DataColumn("ids", Type.GetType("System.Int32"));
dt.Columns.Add(dc0); //将“ids”加入到表中去
//向表中加入“username”列,类型为字符串型(车延禄)
dt.Columns.Add("username",Type.GetType("System.String"));
dt.Columns.Add(“password”);// 向表中加入“password”列,类型为字符串型
dt.Columns["password"].DataType = Type.GetType("System.String");
dt.Columns[0].AutoIncrement AutoIncrement = true;// 设为自增长列
dt.Columns[1].Unique Unique = true;// 设为唯一列
dt.Columns[1].ReadOnly ReadOnly = true;// 设为只读列
dt.Columns[2].DefaultValue DefaultValue = “666666”; //为该列设置默认值
dt.Columns[2].MaxLength MaxLength = 8;// 指定该列的最大长度
DataRow dr1 = DataRow dr1 = dt.NewRow();// 生成一个新行
dr1[1] = "aaa";//向表中加两行数据,ids 为自增长,password 为默认值列
dt.Rows.Add(dr1);
DataRow dr2 = dt.NewRow();
dr2[1] = "bbb";
dt.Rows.Add(dr2);
//显示表中的内容
for(int i=0;i<dt.Rows.Count;i++)
{
for(int j=0;j<dt.Columns.Count;j++;j++)
{
Console.Write(dt.Rows[i][j].ToString()+"\t");
}
Console.Write("\n"); Console.Write("\n");
}
DataSet ds = new DataSet();// 定义一个空的DataSet
ds.Tables.Add(dt);// 将dt表加入到该DataSet 中
DataView数据视图
表示用于排序、筛选、搜索、编辑和导航的DataTable 的可绑定数据的自定义视图,另外,可自定义DataView来表示DataTable中数据的子集。
也可以直接对数据视图进行直接的添加、修改、删除和查询,但这不建议使用,可以直接对表进行操作
实例化DataView对象
DataView dv = new DataView();
dv.Table = ds.Tables[0];
或
DataView dv = ds.Tables[0].DefaultView;
常用属性:
Count:在设置RowFilter和RowStateFilter之后获取的DataView中的记录数量
RowFilter:获取或设置用于筛选在DataView中查看哪些行的表达式。类似于SQL中的where子句
Sort:获取或设置DataView的一个或多个排序列以及排序顺序。类似于SQL中的order by子句
Table:获取或设置源数据表
Constraint数据表的约束
Constraint用来在DataTable的一个或多个DataColumn对象上强制的约束,用于维护DataTable中的数据的完整性的规则
主要包括:
UniqueConstraint:创建主键约束或唯一键约束
ForeignKeyConstraint:创建外键约束
其它完整性约束可以通过DataColumn对象的属性值来进行设置
UniqueConstraint 类:
常用构造函数:
(约束名, 所属的列, 是否设为主键约束)
主要属性:
Columns:该约束所影响到的列
ConstraintName:该约束的名子
IsPrimaryKey:该约束是否为主键
创建主键一般有两种方式:
1.使用DataTable的PrimaryKey属性来设置主键列
DataColumn[] pk = new DataColumn[1];
pk[0] = dt.Columns["ProductID"];
dt.PrimaryKey = pk;
2.使用UniqueConstraint类来设置主键列
DataColumn[] pk = new DataColumn[1];
pk[0] = dt.Columns["ProductID"];
dt.Constraints.Add(new UniqueConstraint("PK_Products", pk[0]),true);
ForeignKeyConstraint 类:
常用构造函数:
(约束名,父表的主键列,子表的外键列)
主要属性:
Columns:该约束所影响的列
RelatedColumns:相关的主表中的主键列
Table:所属的表
RelatedTable:相关的主表
ConstraintName:该约束的约束名
DeleteRule:删除时对应的约束操作
UpdateRule:更新时对应的约束操作
更新和删除约束
Cascade—— 如果更新了父键,就应把新的键值复制到所有的子记录上。如果删除了父记录,也将删除子记录,这是默认选项。
None—— 不执行任何操作,这个选项会留下子数据表中的孤立行。
SetDefault—— 如果定义了一个子记录,那么每个受影响的子记录都把外键码列设置为其默认值。
SetNull—— 所有的子行都把主列设置为DBNull。(按照Microsoft选择的命名约定,主列应是SetDBNull。)
如:
DataTable categories = new DataTable("Categories"); //主表
categories.Columns.Add(new DataColumn("CategoryID", typeof(int)));
categories.Columns.Add(new DataColumn("CategoryName", typeof(string)));
categories.Columns.Add(new DataColumn("Description", typeof(string)));
categories.Constraints.Add(new UniqueConstraint("PK_Categories",categories.Columns["CategoryID"],true));(车延禄)
DataColumn parent = ds.Tables["Categories"].Columns["CategoryID"];
DataColumn child = ds.Tables["Products"].Columns["CategoryID"]; //从表中的列
ForeignKeyConstraint fk = new ForeignKeyConstraint("FK_Product_CategoryID", parent, child);
fk.UpdateRule = Rule.Cascade;
fk.DeleteRule = Rule.SetNull;
ds.Tables["Products"].Constraints.Add(fk);
数据关系DataRelation
我们可以在数据集中设置表与表之间的关系,有了表与表之间的关系,我们就可以从子表中取出对应主表中的数据,或从主表中取出对应子表中的数据来。
DataRelation对象属于DataSet而不是属于DataTable。另外,在这里的外键约束也不同于数据关系,外键约束只是为了保证数据集中表的引用完整性,而数据关系是为了实现表和表之间的互访问。
创建数据关系:
DataSet ds = new DataSet("Relationships");
ds.Tables.Add(CreateBuildingTable());(车延禄)
ds.Tables.Add(CreateRoomTable());
ds.Relations.Add("Rooms",ds.Tables["Building"].Columns["BuildingID"],ds.Tables["Room"].Columns["BuildingID"]);
并遍历数据关系,列出Rooms表中所有的子行
foreach(DataRow theBuilding in ds.Tables["Building"].Rows)
{
DataRow[] children = theBuilding.GetChildRows("Rooms");
foreach(DataRow theRoom in children)
Console.WriteLine("Room: {0}", theRoom["Name"]);
}
数据适配器DataAdapter:
数据适配器是内存中的数据集与硬盘数据库之间的桥梁,通过数据适配器可以把数据库中的数据加载到内存中的数据集中,也可以把内存中的数据集中修改完的数据更新到数据库去。
只要我们设置好我们的DataAdapter,我们就可以实现数据集的离线操作。我们可以先把数据库中的相关数据加载到内存的数据集中,然后就可以断开与数据库中的连接,在内存中的数据集对数据进行操作,而不直接提交到数据库中。等到对内存中数据集操作完成后,就可以打开对数据库的链接,把内存中数据集的修改结果一起全更新到数据库中去。这种思路比较适用于移动办公与SmartClient技术。
SqlDataAdapter对象的实例化:
SqlDataAdapter SqlAdapter = new SqlDataAdapter();
在实例化了SqlDataAdapter对象后,此SqlDataAdapter仍然是一个没有实际作用的数据适配器,因为它对数据库和数据集的操作实际上是通过它的四个SqlCommand对象(SelectCommand,InsertCommand,UpdateCommand,DeleteCommand)来实现的。所以我们实例化了SqlDataAdapter对象后需要再实例化它相关的SqlCommand对象
SqlAdapter.SelectCommand = new SqlCommand();
SqlAdapter.InsertCommand = new SqlCommand();
SqlAdapter.UpdateCommand = new SqlCommand();
SqlAdapter.DeleteCommand = new SqlCommand();
这四个SqlCommand的引用实际都指向一个SqlCommand对象的实例,我们还需要对每一个SqlCommand对象的相关属性(Connection,CommandType,CommandText,Parameters)进行设置,在此请参照SqlCommand的相关知识
SqlDataAdapter对象的两个方法:
Fill(DataSet,string);//SqlDataAdapter自动调用它的SelectCommand对象,查询硬盘数据库,把查询的结果添充到内存数据集中
Update(DataSet,string);//SqlDataAdapter根据内存数据集中数据的变化情况,自动调用其相应的InsertCommand,DeleteCommand,UpdateCommand把相应的数据更新到硬盘数据库中去。
使用SqlDataAdapter对象要注意的问题:
a.SqlDataAdapter对象包含四个SqlCommand对象,这四个SqlCommand对象具有与普通的SqlCommand一样的属性和方法。(车延禄)
b.SqlDataAdapter对象的实例化需使用new关键字实现,在实例化了SqlDataAdapter对象后会自动产生四个SqlCommand对象的引用,但这四个SqlCommand对象并没有实例化与初始化,需要一一对其实例化与初始化。
c.SqlDataAdapter对象的Fill()方法会自动调用其SelectCommand对象,Update()方法会自动调用其InsertCommand,UpdateCommand,DeleteCommand。
d.使用SqlDataAdapter对象操作数据库时不需要显式的打开与关闭连接。Fill()和Update()执行的时候会自动打开与关闭链接。
e.使用SqlDataAdapter对象操作数据库的实质是SqlDataAdapter对象调用它的四个SqlCommand对象来实现的。
f.对内存中数据集的操作只能使用DataSet及其子对象的属性与方法来实现,而对数据库操作则要使用Sql语句来实现。
如:
private static SqlCommand GenerateSelectCommand(SqlConnection conn )
{
SqlCommand aCommand = new SqlCommand("RegionSelect" , conn);
aCommand.CommandType = CommandType.StoredProcedure;
aCommand.UpdatedRowSource = UpdateRowSource.None;
return aCommand;
}
DataSet ds = new DataSet();
SqlDataAdapter da = new SqlDataAdapter();
da.SelectCommand = GenerateSelectCommand (conn);
da.Fill(ds , "Region");
弱类型DataSet与强类型DataSet(车延禄)
当我们用SqlDataAdapter对象的Fill()方法查询数据时,会自动在内存中的DataSet中创建表并创建相应的列和行,这样创建的DataSet是一个弱类型的。它的数据表中的每个数据都是object类型,虽然object类型的数据可以接受任何数据,但这也正是弱类型的缺陷,由于它对数据类型的限制不严格,会导致里面可以存储错误的数据类型,以致于在更新数据的时候产生错误。另外,它是object类型,在操作数据的时候,往往需要进行拆箱与装箱,而拆箱与装箱的过程对系统资源消耗很大,会从某种程序上降低程序的性能。
正是由于弱类型的DataSet有上面这些缺陷,我们建议在使用强类型DataSet。所谓的强类型的DataSet,就是事先建立一个派生自DataSet的自定义DataSet,并在其中添加指定的表、列,并指定其表和列的数据类型。然后使用表映射与列映射把它与数据库中的表和列进行对应。这样就不会出现弱类型DataSet所出现的问题了。
DataSet的问题
虽然DataSet与SqlDataAdapter二者对数据库的操作可以称之谓“最佳拍档”,是ADO.NET中新增的数据访问方式。但对大部分开发人员来说,对这种数据访问并不太看好。其中一个很大的原因,就是这种DataSet是把数据库中的二维表再加载到内存中的二维表。这种内存中的二维表并不总适合于“对象世界”的程序代码,在对于复杂的业务逻辑它处理起来往往显得笨拙复杂。所以我们往往更倾向于实体类与泛型集合结合来实现数据访问,或者把DataSet与实体类进行结合进行数据操作,关于实体类和泛型集合的设计与使用,在下一篇中进行阐述。