DataSet
DataAdapter
DataRow
DataColumn
DataView
DataRowView
更新数据源
使用CommandBuilder自动更新数据源
手动Update
Update的事件
RowUpdating 和 RowUpdated
FillError
ADO.NET中的映射
表映射
列映射
Fill方法总结
Update方法总结
如何为表定义主键
定义多个主键
如何添加约束
常见问题总结
DataSet
DataSet是支持Ado.NET的断开式和分布式数据方案的核心对象,它是数据在内存中驻留的表示形式,它可用于多种不同的数据源,用于XML数据,或用于管理应用程序本地数据;
DataSet表示包括相关表、约束和表间关系在内的整个数据集.
DataSet 用于执行以下功能:
l 在应用程序中将数据缓存在本地,以便可以对数据进行处理。如果只需要读取查询结果,DataReader 是更好的选择。
l 在层间或从 XML Web 服务对数据进行远程处理。
l 与数据进行动态交互,例如绑定到 Windows 窗体控件或组合并关联来自多个源的数据。
l 对数据执行大量的处理,而不需要与数据源保持打开的连接,从而将该连接释放给其他客户端使用。
DataAdapter 使用 DataReader 来填充 DataSet 的内容(参考MSDN)
示例:
//创建一个DataSet对象.(如果你没有为它指定名字,则自动生成"NewDataSet")
ds = new DataSet("MyDataSet");
注意:如果DataSet中只存在一个表,是不需要对表名区分大小写的.反之,则要区分.
DataAdapter
DataAdapter在数据源和DataSet之间,起着桥梁的作用.
DataAdapter的Fill 方法使用 DataReader 对象来隐式地返回用于在 DataSet 中创建表的列名称和类型以及用于填充 DataSet 中的表行的数据。表和列仅在不存在时才创建;否则,Fill 将使用现有的 DataSet 架构。如果 Fill 发现某个表存在主键,对于主键列的值与从数据源返回的行的主键列的值匹配的行,将使用数据源中的数据重写 DataSet 中的数据。如果未找到任何主键,则数据将追加到 DataSet 中的表。主键不会创建,除非主键存在于数据源中并且 DataAdapter.MissingSchemaAction 设置为 MissingSchemaAction.AddWithKey。
如果DataAdapter遇到多个结果集,将在DataSet中自动创建多个表,如果在Fill中指定表名,则以YourTableNameN递增表名,如果没有指定,则以TableN递增;
你可以使用多个DataAdapter来填充一个DataSet,这样,你就可以连接多种类型的数据库,却放在同一个DataSet中,并还能关联它们(比如对两个来自不同数据库的表进行DataRelation).
示例:注意,例子中并没有显式的使用conn.Open()/Close(),其实当Fill发现连接尚未打开时,它将自动隐式的打开SqlDataAdapter正在使用的连接,并且Fill还将在完成时自动隐式的关闭连接.并且Fill方法会在填充后,默认情况下会自动将所有行设置为UnChanged.(是否自动设置为UnChanged,由DataAdapter的AcceptChangesDuringFill属性控制.如果AcceptChangesDuringFill=false,那么新添加的行状态就是Added)
conn = new SqlConnection(strConn);
ds = new DataSet();
SqlDataAdapter da1 = new SqlDataAdapter("SELECT * FROM Products", conn);
da1.Fill(ds, "Products");
//将指定行Fill到DataSet中
//0:表示Fill的行标 1:表示要填充的行数
da.Fill(ds, 0, 1, "Product");
注意:应显式Open/Close连接,尽可能短的减少连接时间;
DataRow
使用DataRow对象及其属性和方法检索、评估、插入、删除和更新DataTable中的值.
DataRow对象没有直接在代码中使用的构造函数,一般使用DataTable的NewRow()方法来新建.
一个DataRow对象被创建后,其状态是Detached(比如 DataRow row = table.NewRow()时),是孤立存在的;当table.Rows.Add(row)后,其状态就转变成了Added;当修改了这个DateRow(比如:table.Rows[0][“NAME”]=”bush”)以后,其状态就转变成了Modified;当调用table.Delete(row)方法后,其状态就转变成了Deleted,不过此行依然存在于DataTable中,如果你Count行数,依然没有变的,只有调用了AcceptChanges后才真正删除,或是你调用table.Remove(row),此行即可一步删除,此时状态也回归到Detached.
一旦调用了AcceptChanges()方法后,Added、Modified、Unchanged将保留当前值(只有Current值了),Deleted状态的行将从DataTable中移除,最后所有行的状态都是Unchanged.
Fill方法会自动调用AcceptChanges()方法,将DataTable中的行全部置于Unchanged状态;如果Fill方法中指定的那个DataSet中的DataTable不存在时,它将新生成一个同数据源同结构的DataTable.
DataRow的RowState属性
RowState |
说明 |
Unchanged |
自上次调用 AcceptChanges 之后,或自 DataAdapter.Fill 创建了行之后,未做出过任何更改。 |
Added |
已将行添加到表中,但尚未调用 AcceptChanges。 |
Modified |
已更改了行的某个元素。 |
Deleted |
已将该行从表中删除,并且尚未调用 AcceptChanges。 |
Detached |
该行不属于任何 DataRowCollection。新建行的 RowState 设置为 Detached。通过调用 Add 方法将新的 DataRow 添加到 DataRowCollection 之后,RowState 属性的值设置为 Added。 对于已经使用 Remove 方法(或是在使用 Delete 方法之后使用了 AcceptChanges 方法)从 DataRowCollection 中移除的行,也设置为 Detached。 |
注意: DataRowCollection和 DataRow 的关系:DataRowCollection表示DataTable中实际DataRow的对象.(见下例)
//加上索引,就是DataRowCollection(集合)中的某一行了,故用DataRow对象表示
DataRow row = table.Rows[1];
row["姓名"] = "蝙蝠侠";
//table.Rows 的返回值就是 DataRowCollection(集合)
DataRowCollection rowC = table.Rows;
rowC[1]["姓名"] = "蝙蝠侠";
DataColumn
DataColumn是用于创建DataTable的架构的基本构造块.每个DataColumn都有DataType属性,DataTable中各列的属性必须与数据源中列的属性相匹配.
DataColumn的各属性示例:
DataColumn ID = new DataColumn("学号");
ID.AutoIncrement = true;
//ID.AutoIncrementSeed = 5;//自动增列的起始值
//ID.AutoIncrementStep = 5;//增量
DataColumn Name = new DataColumn("姓名");
Name.AllowDBNull = false;//不可为空(默认是 可为空 的)
Name.DataType = typeof(System.String);
//Name.Unique = true;//不可重名
DataColumn Age = new DataColumn("年龄");
Age.DataType = typeof(System.Int32);
Age.DefaultValue = 10;
Age.ReadOnly = true;//不能被编辑了
DataColumn Grade = new DataColumn("总成绩");
Grade.DataType = typeof(System.Int32);
Grade.Expression = "年龄*10"; //似乎有了 Expression,也默认 ReadOnly 了
DataColumn Birthday = new DataColumn("出生日期");
Birthday.DataType = typeof(DateTime);//日期时间类型
注意: Expression 属性,可以使用表达式(见上例),也可以创建聚合列(见下例).如果order表有个叫detail的子表,两表间通过order.orderid 和 detail.orderid两个列建立一个DataRelation的对象”order2detail”,在主表order中就可以建立一个聚合列,计算每个order在detail表中含有的所有item的价格之和:
DataColumn SumPrice = new DataColumn();
//child(order2detail) 表示通过order2detail关系,找到相关子表内容
SumPrice.Expression = "sum(child(order2detail).price)";
DataView
DataView表示用于排序、筛选、搜索、编辑和导航的DataTable的可绑定数据的自定义视图.不过DataView只能对某一个DataTable建立视图(也就是说它是建立在DataTable基础上的).
RowStateFilter属性用来设置DataView中的行状态筛选器,它使用DataViewRowState枚举值.
DataViewRowState枚举
成员名称 |
说明 |
Added |
一个新行。 |
CurrentRows |
包括未更改行、新行和已修改行的当前行。 |
Deleted |
已删除的行。 |
ModifiedCurrent |
当前版本,原始数据(请参见 ModifiedOriginal)的修改版本。 |
ModifiedOriginal |
原始版本(尽管它后来已被修改并以 ModifiedCurrent 形式存在)。 |
None |
无。 |
OriginalRows |
包括未更改行和已删除行的原始行。 |
Unchanged |
未更改的行。 |
注意: 如果将 RowStateFilter 设置为 ModifiedCurrent 或 CurrentRows,新行也将是可见的。
如果将 RowStateFilter 设置为 ModifiedOriginal 和 OriginalRows,已删除的行也将是可见的。
示例:
添加一行
//创建DataView对象①
//DataView dv = new DataView(ds.Tables["Product"]);
//创建DataView对象②
DataView dv = ds.Tables["Product"].DefaultView;
DataRowView drv = dv.AddNew();
drv["ProductID"] = "001";
drv["ProductName"] = "waterbox";
drv.EndEdit();
GridView1.DataSource = dv;
GridView1.DataBind();
RowFilter属性
DataView dv = ds.Tables["Product"].DefaultView;
//返回ProductName=waterbox的行
dv.RowFilter = "ProductName=waterbox";
//返回那些具有空值的列
dv.RowFilter = "Isnull(Col1,'Null Column') = 'Null Column'";
GridView1.DataSource = dv;
GridView1.DataBind();
RowStateFilter属性
DataView dv = new DataView(ds.Tables["Product"]);
dv.RowStateFilter = DataViewRowState.ModifiedCurrent | DataViewRowState.Added;
GridView1.DataSource = dv;
GridView1.DataBind();
DataRowView
DataRowView用来表示一行数据(比如在DataTable中) .
DataRowView 可以具有以下四个不同的版本状态之一:Default、Original、Current 和 Proposed。
在针对 DataRow 调用 BeginEdit 之后,任何已编辑的值变成 Proposed 值。在调用 CancelEdit 或 EndEdit 之前,行有一个 Original 和一个 Proposed 版本。如果调用了 CancelEdit,建议的版本将被丢弃,值将恢复为 Original。如果调用了 EndEdit,DataRowView 将不再具有 Proposed 版本;相反,建议的值将变为当前值。默认值只在那些列具有已定义的默认值的行上可用。
示例
DataView dv = ds.Tables["运货商"].DefaultView;
//得到第一行所有内容
DataRowView drv = dv[0];
drv["公司名称"] = "皮包公司";
GridView1.DataSource = dv;
GridView1.DataBind();
更新数据源
使用CommandBuilder自动更新数据源
CommandBuilder只适合单表操作;
为了自动生成命令,必须设置SelectCommand属性(由于每次都会执行一次SelectCommand,会降低性能,推荐使用手动更新),根据SelectCommand属性所要检索的表架构自动生成INSERT,UPDATE和DELETE语句的语法;
SelectCommand属性中必须包括至少一个主键或唯一列,否则将出现异常;
如果已手动设置了某属性的Command(如UpdateCommand),则使用手动设置的Command;
如果列名或表名称中包含特殊字符(如空格、句点、问号或其它非字母数字字符),即使这些字符都用括号分隔,自动更新也会失败.
示例
//构造函数的第一个参数即为 SelectCommand
SqlDataAdapter da = new SqlDataAdapter("SELECT* FROM Product", conn);
SqlCommandBuilder builder = new SqlCommandBuilder(da);
//注意如"INSERT INTO [Product]([ProductID],[ProductName]) VALUES (@para1,@para2)"中的"[]"号了吗?就是设置这个
builder.QuotePrefix = "[";
builder.QuoteSuffix = "]";
DataSet ds = new DataSet();
conn.Open();
da.Fill(ds, "Product");
//添加一行数据,更新时将自动生成 InsertCommand
DataRow row = ds.Tables["Product"].NewRow();
row["ProductID"] = "001";
row["ProductName"] = "waterbox";
row.AcceptChanges();
da.Update(ds, "Product");
conn.Close();
手动Update
Fill方法通过检查DataSet中行的主键值及SelectCommand返回的行来确定是要添加一个新行还是更新现有行.如果Fill发现DataSet中某行的主键值与SelectCommand返回结果中某行的主键值相匹配,则它将用SelectCommand返回的行中的信息更新现有行,并将现有行的RowState设置为Unchanged;如果SelectCommand返回的行所具有的主键值与DataSet中行的任何主键值都不匹配,则Fill方法将添加RowState为Unchanged的新行.
要处理在调用Update方法时可能发生的异常,可以使用RowUpdated事件响应更新行时发生的错误;也可以在调用Update之前将DataAdapter.ContinueUpdateOnError设置为true,并在更新完成后响应特定行的RowError属性中存储的错误信息.
如果 SelectCommand 返回 OUTER JOIN 的结果,则 DataAdapter 不会为生成的 DataTable 设置 PrimaryKey 值,您必须自己定义 PrimaryKey 以确保正确解析重复行.
Update后,会默认自动将所有行设置为Unchanged状态,如想控制这一自动行为,请设置DataAdapter的属性AcceptChangesDuringUpdate.
避免自动增量值冲突
DataSet 使您可标识那些添加新行时自动对其值进行递增的列。在DataSet 中使用自动增量的列时,如果自动增量的列来自数据源,可避免添加到DataSet 的行和添加到数据源的行之间本地编号冲突。
例如一个表,它的主键列CustomerID 是自动增量的。两个新的客户信息行添加到表中,并接收到自动增量的CustomerID 值1 和2。然后,只有第二个客户行被传递给DataAdapter 的方法Update,新添加的行在数据源接收到一个自动增量的CustomerID 值1,与DataSet 中的值2 不匹配。当DataAdapter 用返回值填充表中第二行时,就会出现约束冲突,因为第一个客户行已经使用了CustomerID 值1。
要避免这种情况,建议把DataSet 中的列创建为AutoIncrementStep 值等于-1 并且AutoIncrementSeed 值等于0,另外,还要确保数据源生成的自动增量标识值从1 开始,并且以正阶值递增。
示例:
更新
da.UpdateCommand = new SqlCommand("UPDATE Product SET ProductName = @ProductName WHERE ProductID = @oldProductID", myConn);
da.UpdateCommand.Parameters.Add("@ProductName", SqlDbType.NVarChar, 15, "ProductName");
SqlParameter para = da.UpdateCommand.Parameters.Add("@oldProductID", SqlDbType.Int);
//SourceColumn 是将从其中检索 Parameter 值的 DataRow 的 DataColumn 的名称. (From MSDN)
//SourceColumn 告诉UpdateCommand,当要从数据库更新值时,应从这一列来获取相关值. (From Google) para.SourceColumn = "ProductID";
//Original值是曾用来从数据源填充到DataTable的值
//因为当从数据源 Fill 到Table中时,两者的结构和数据都是一致的.
//比如 1 waterbox(from datasource) , 1 waterbox(from table)
//而如果当你修改了行中能标识唯一行的字段的值时
//比如 1 waterbox(from datasource) , 11 waterbox(from table and original=1)
//你再使用Where表达示,就会将数据源中的产品ID是11的行数据更新,就不对了.
//所以,你需要使用它的 Original 值
para.SourceVersion = DataRowVersion.Original;
DataRow row = ds.Tables["Product"].Rows[0];
row["ProductName"] = "WaterBox";
da.Update(ds, "Product");
GridView1.DataSource = ds.Tables["Product"];
GridView1.DataBind();
插入
da.InsertCommand = new SqlCommand("INSERT INTO Product(ProductName) VALUES (@ProductName)", myConn);
da.InsertCommand.Parameters.Add("@ProductName", SqlDbType.NVarChar, 15, "ProductName");
DataRow row = ds.Tables["Product"].NewRow();
row["ProductName"] = "UFO";
ds.Tables["Product"].Rows.Add(row);
da.Update(ds, "Product");
GridView1.DataSource = ds.Tables["Product"];
GridView1.DataBind();
删除
da.DeleteCommand = new SqlCommand("DELETE FROM Product WHERE 产品ID=@ProductID", myConn);
da.DeleteCommand.Parameters.Add("@ProductName", SqlDbType.NVarChar, 40, "ProductName");
SqlParameter para = da.DeleteCommand.Parameters.Add("@ProductID", SqlDbType.Int);
para.SourceColumn = "ProductID";
para.SourceVersion = DataRowVersion.Original;
DataRow row = ds.Tables["Product"].Rows[69];
row.Delete();
da.Update(ds, "Product");
GridView1.DataSource = ds.Tables["Product"];
GridView1.DataBind();
过滤指定要更新的行的子集(可以控制处理插入、更新和删除的顺序)
da.Update(ds.Tables["Product"].Select(null,null, DataViewRowState.Added));
da.Update(ds.Tables["Product"].Select(null,null, DataViewRowState.ModifiedCurrent));
da.Update(ds.Tables["Product"].Select(null,null, DataViewRowState.Deleted));
注意:如果对DataSet、DataTable和DataRow调用AcceptChanges,则将使DataRow中的所有Original值都被重写为该DataRow的Current值.如果将该行标识为唯一行的字段值被修改了,那么当调用AcceptChanges后,Original值将不再匹配数据源中的值.
下表显示创建自动生成命令的规则。
命令 |
规则 |
InsertCommand |
在数据源处为表中所有 RowState 为 Added 的行插入一行。插入所有可更新列的值(但是不包括标识、表达式或时间戳等列)。 |
UpdateCommand |
在数据源处为表中所有 RowState 为 Modified 的行更新行。更新所有列的值,不可更新的列除外,例如标识列或表达式列。更新符合以下条件的所有行:数据源中的列值匹配行的主键列值,并且数据源中的剩余列匹配行的原始值。有关更多信息,请参见本主题后面的“更新和删除的开放式并发模型”。 |
DeleteCommand |
在数据源处为表中所有 RowState 为 Deleted 的行删除行。删除符合以下条件的所有行:列值匹配行的主键列值,并且数据源中的剩余列匹配行的原始值。有关更多信息,请参见本主题后面的“更新和删除的开放式并发模型”。 |
Update的事件
ADO.NET DataAdapter 公开了三个可用于响应对数据源中的数据所作更改的事件。下表显示了这些 DataAdapter 事件。
事件 |
说明 |
RowUpdating |
将要开始对某行执行 UPDATE、INSERT 或 DELETE 操作(通过调用 Update 方法之一)。 |
RowUpdated |
对某行的 UPDATE、INSERT 或 DELETE 操作(通过调用 Update 方法之一)已完成。 |
FillError |
在 Fill 操作过程中出错。 |
RowUpdating 和 RowUpdated
在数据源中处理对DataSet中某行的任何更新之前,将引发RowUpdating;在数据源中处理对DataSet中某行的任何更新之后,将引发RowUpdated.
RowUpdating通常用于更新前检查当前数据源是否已被其它用户修改;RowUpdated通常用于响应在更新过程中发生的错误和异常.
RowUpdatedEventArgs 属性
名称 |
说明 |
Command |
获取调用 Update 时执行的 IDbCommand。 |
Errors |
获取当 Command 执行时 .NET Framework 数据提供程序生成的任何错误。 |
RecordsAffected |
通过执行 SQL 语句获取更改、插入或删除的行数。 |
Row |
获取通过 Update 发送的 DataRow。 |
RowCount |
获取在一批更新后的记录中处理的行数。 |
StatementType |
获取所执行的 SQL 语句的类型。 |
Status |
获取 Command 属性的 UpdateStatus。 |
TableMapping |
获取通过 Update 发送的 DataTableMapping。 |
RowUpdatingEventArgs 属性
名称 |
说明 |
Command |
获取要在 Update 操作过程中执行的 IDbCommand。 |
Errors |
获取当 Command 执行时 .NET Framework 数据提供程序生成的任何错误。 |
Row |
获取要作为插入、更新或删除操作的一部分发送到服务器的 DataRow。 |
StatementType |
获取要执行的 SQL 语句的类型。 |
Status |
获取或设置 Command 属性的 UpdateStatus。 |
TableMapping |
获取要通过 Update 发送的 DataTableMapping。 |
可以使用Status属性来确定该操作过程中是否出错.如果将Statue属性设置为ErrorsOccurred,当引发异常,除此之外的其它值都不会引发异常.
你也可以使用ContinueUpdateOnError属性,当ContinueUpdateOnError=true时, 则在更新行过程中遇到错误时不引发异常。跳过该行的更新,并将错误信息置于出错行的 RowError 属性中。DataAdapter 继续更新后面的行。
UpdateStatus枚举
状态 |
说明 |
Continue |
继续执行更新操作。 |
ErrorsOccurred |
中止更新操作并引发异常。 |
SkipCurrentRow |
忽略当前行并继续执行更新操作。 |
SkipAllRemainingRows |
中止更新操作但不引发异常。 |
FillError
Fill过程中出错时,DataAdapter将发出FillError事件.通常当所添加行中的数据必须损失一些精度才能转换成.NET Framework类型时,会发生这类型的错误.
如果Fill操作过程中出错了,则当前行将不会被添加到DataTable中.
示例:
RowUpating
da.RowUpdating += new SqlRowUpdatingEventHandler(da_RowUpdating);
//每更新一行前,就会触发一次OnUpdateing,主要用于更新前判断数据源是否已被其它用户修改.
void da_RowUpdating(object sender, SqlRowUpdatingEventArgs e)
{
switch (e.StatementType)
{
case StatementType.Update:
{
SqlConnection conn = new SqlConnection(
"Data Source=(local);Database=Data;Integrated Security=SSPI;");
//用数据集中的Original值,到数据源中查找,如果查找不到,说明已被其它用户修改了.
string strSql = "SELECT * FROM Product WHERE ProductName='" +
e.Row["ProductName", DataRowVersion.Original] + "'";
SqlCommand com = new SqlCommand(strSql, conn);
conn.Open();
if (com.ExecuteNonQuery() == 0)
{
Response.Write("更新出错!数据已被其它用户修改.");
e.Status = UpdateStatus.ErrorsOccurred;
}
conn.Close();
break;
}
}
}
da.RowUpdating -= new SqlRowUpdatingEventHandler(da_RowUpdating);
RowUpated
da.RowUpdated += new SqlRowUpdatedEventHandler(da_RowUpdated);
//更新一行后,就触发一次OnRowUpdated,此例用于更新出错时,是否忽略错误继续更新
void da_RowUpdated(object sender, SqlRowUpdatedEventArgs e)
{
switch (e.StatementType)
{
case StatementType.Update:
{
if (e.Status == UpdateStatus.ErrorsOccurred)
{
//将错误信息添加到DataRow中去(模仿 ContinueUpdateOnError = true 的行为)
e.Row.RowError = e.Errors.Message;
e.Status = UpdateStatus.SkipCurrentRow;
}
break;
}
}
}
da.RowUpdated -= new SqlRowUpdatedEventHandler(da_RowUpdated);
FillError
da.FillError += new FillErrorEventHandler(da_FillError);
void da_FillError(object sender, FillErrorEventArgs e)
{
if (e.Errors.GetType() == typeof(System.OverflowException))
{
Response.Write(e.Errors.Message);
e.Continue = true; //继续填充 DataTable
}
}
da.FillError -= new FillErrorEventHandler(da_FillError);
ADO.NET中的映射
表映射是控制数据适配器如何将数据表和数据列从一个物理数据源复制到ADO.NET内存中对象的过程.数据适配器对象利用”填充”(Fill)方法将”选择”命令检索的数据填充到”数据集”或”数据表”对象.在内部,”填充”方法使用数据读取器(DataReader)来读取描述源表结构和内容的数据和元数据,然后,读取的数据被复制到临时的内存容器(即数据表)中.通过表映射机制,你可以控制SQL结果集如果映射到内存的对象中.
映射机制
一旦”选择”命令终止,且数据适配器返回一个或多个结果集后,映射机制就开始起作用了.
将结果集映射到数据集的过程包括两个阶段: 表映射 列映射
表映射
//如果适配器中只有一个表,那么它将被映射为Table,如果有多个表,则依次递增Table1,Table2...
da.Fill(ds);
//如果适配器中只有一个表,那么它将被映射为Product,如果有多个表,则依次递增Product1,Product2...
da.Fill(ds, "Product");
适配器检查TableMappings集合,如果不存在与结果集名称匹配条目,则默认情况下,会创建该对象,并填充数据;如果存在匹配条目,则找到它,并将结果集中的内容与其合并.
DataTableMapping对象描述了结果集与数据集中的”数据表”对象之间的映射关系.
DataTableMapping map1, map2;
map1 = da.TableMappings.Add("Table", "Product");
map2 = da.TableMappings.Add("Table1", "Order");
//通常如果您前面使用表映射了,那么Fill只需要一个DataSet参数即可,否则前面的映射则无效.
da.Fill(ds);
//DataTableMapping.SourceTable 设置/返回默认的结果集名称
string SourceTable1 = map1.SourceTable; //SourceTable1 = Table
string SourceTable2 = map2.SourceTable; //SourceTable2 = Table1
//DataTableMapping.DataSetTable1 设置/返回映射名称
string DataSetTable1 = map1.DataSetTable; //DataSetTable1 = Product
string DataSetTable2 = map2.DataSetTable; //DataSetTable2 = Order
其实如果你只是为了给数据集表起一个好听的名子,使用下面代码也能达到映射的作用.
da.Fill(ds);
ds.Tables[0].TableName = "Product";
ds.Tables[1].TableName = "Order";
列映射
string strSql = "SELECT ProductID,ProductName,Quantity,Price FROM 产品";
…
DataTableMapping map1,map2,map3;
map1 = da.TableMappings.Add("Data", "Product");
map1.ColumnMappings.Add("ProductID ", "产品ID");
map1.ColumnMappings.Add("ProductName ", "产品名称");
map1.ColumnMappings.Add("Quantity", "数量");
map1.ColumnMappings.Add("Price", "价格");
map1 = da.TableMappings.Add("Data1", "Customer");
map1 = da.TableMappings.Add("Data2", "Order");
//推荐使用带有两参数的Fill;好处是,不使用默认表名Table,在多表时,不会造成冲突!
da.Fill(ds, "Data”);
默认情况下,目录DataColumn的名称与数据源列名称是相同的.整个映射是在”填充”方法内自动进行的,当Fill终止时,映射也终止了,您接下来可以做的事情就是加入进一步的更改,比如:关系、约束、主键、只读、自动递增、步长和对空值的支持等等.
“填充”方法主要完成两个操作:首先,它将源结果集映射到内存中的表;其次,它使用从物理数据源提取的数据来填充表.但在完成其实任何一个任务的时候,”填充”方法都有可能引发一种轻量级的异常.
通常会引发下列两种轻量级的异常: 缺少映射 缺少架构
缺少映射操作
如果在TableMappings集合中没有找到默认的表名称,或是在ColumnMappings集合中没有找到列名,则要进行缺少映射的操作.
通常情况下, MissingMapping枚举的值默认是 Passthrough ,就是当未在数据集中发现匹配的表名或列名时,程序会自动创建新的对象.如果MissingMapping枚举的值设置为 Ignore ,那么忽略,不会检测出任何错误,但数据集中也不会有被填充的新数据,基本上就等同于什么都没做.
MissingMapping枚举
成员名称 |
说明 |
Error |
如果缺少指定的列映射,则生成 InvalidOperationException。 |
Ignore |
忽略没有映射的列或表。返回 空引用(在 Visual Basic 中为 Nothing)。 |
Passthrough |
创建源列或源表,并使用其原始名称将其添加到 DataSet。 |
缺少架构操作
一旦适配器完成映射阶段,就开始用所选的结果集的内容填充数据集.如果目标数据集中不存在匹配的”数据表”或DataColumn对象,就会触发另一个轻量级异常:缺少架构操作.
(是不是可以酱紫理解 ho,如果说, MissingMapping=Passthrough,就不会引发”缺少架构”的操作)
通常情况下, MissingSchemaAction枚举的默认值是Add,在未发现表或列的情况下自动创建架构并填充.不过要记住,这种方式添加的架构信息非常有限,只包括名称和类型.如需要额外的,如主键、自动递增、只读、和空设置等等,请使用AddWithKey.不过,即使使用了AddWithKey,也不能将列的所有可用信息都加载到DataColumn.例如: AddWithKey可以将某个列标记为自动递增,但却不设置相关的初始值和步长,且源列的默认值也不会自动复制,主键被导入,但并非你可能设置的任何额外索引也都被导入.
(引用自DataAdapter:主键不会创建,除非主键存在于数据源中并且 DataAdapter.MissingSchemaAction 设置为 MissingSchemaAction.AddWithKey)
MissingSchemaAction枚举
成员名称 |
说明 |
Add |
默认值:添加必需的列以完成架构。 |
AddWithKey |
添加必需的列和主键信息以完成架构。 |
Error |
如果缺少指定的列映射,则生成 InvalidOperationException。 |
Ignore |
忽略未映射的列。 |
FillSchema
该方法会返回一个包含”数据表”对象架构(没有数据)的数组.
SchemaType
成员名称 |
说明 |
Mapped |
将任何现有的表映射应用到传入架构。用转换的架构配置 DataSet。 |
Source |
忽略 DataAdapter 上的任何表映射。使用传入架构配置 DataSet,而不应用任何转换。 |
在使用DataAdapter.Fill方法后,DataTable和DataColumn对象的多种属性(如主键、自动增加字段、可为空值字段、唯一索引等等)都没有设置.Fill方法只检索显示数据所需要的那些架构.如果你需要其他那些信息,就必须采取额外的步骤才能获取更新或验证对象所需的其他架构.
要获取有关DataSet对象的其他信息,可使用下面任一方法:
1. 调用DataAdapter.FillSchema方法来获取更多架构信息.
2. 在调用Fill方法前,将DataAdapter.MissingSchemaAction设置MissingSchemaAction.AddWithKey.
那么何时使用FillSchema方法,何时使用MissingSchemaAction属性?
多次调用Fill()方法的情况下,比较适宜使用FillSchema().如果使用MissingSchemaAction,那么在每次调用Fill()时架构都被重新设置.
如果使用多条SQL语句返回多个记录集时适宜使用MissingSchemaAction,因为FillSchema只返回第一个记录集的构架.另外,如果你只调用一次Fill()方法,适宜使用MissingSchemaAction.
如果你需要的是一个只读的DataSet时,两者都不要用,因为那是多余的.
如果你在设计时生成DataSet,两者都不要用,因为设计器已经生成架构了.
如果你打算从XML加载数据,并且希望使用XML的构架,两者都不要用.
使用映射达到管理用户权限的目的
DataTableMapping map1;
string user = "Client";
map1 = da.TableMappings.Add("Table", "Product");
if (user == "Administrator")
{
map1.ColumnMappings.Add("产品ID", "ProductID");
map1.ColumnMappings.Add("产品名称", "ProductName");
map1.ColumnMappings.Add("单位数量", "Quantity");
map1.ColumnMappings.Add("单价", "Price");
}
if (user == "Client")
{
map1.ColumnMappings.Add("产品ID", "ProductID");
map1.ColumnMappings.Add("产品名称", "ProductName");
}
da.MissingMappingAction = MissingMappingAction.Ignore;
da.MissingSchemaAction = MissingSchemaAction.Add;
//通常如果您前面使用表映射了,那么Fill只需要一个DataSet参数即可,否则前面的映射则无效.
da.Fill(ds);
注意:上面提到的 结果集:是用SELECT从数据源中得到的数据 数据集:则是DataSet中的数据
MissingMapping 默认值 自动创建表/列映射
MissingSchemaAction 默认值 自动创建表/列结构 结构建立在映射之上
映射总结
映射反映了适配器是如何将数据源中的表和列(架构)复制到ADO.NET内存中对象的过程;
映射发生在Fill方法中;当适配器返回结果集后,映射就开始了;适配器会去TableMappings集合中检索是否存在匹配项,来决定是要合并还是新建;
如果处理映射中遇到的轻量级的异常;如果让映射到ADO.NET内存对象中的主键包含更多信息;如果有选择性的映射部分列,从而达到管理权限的目的.
Fill方法总结
Fill所做的三件事: DataReader、表/列映射、填充数据
1. 隐式的调用DataReader,从数据源读取架构信息与内容;
2. 表/列映射:当”选择”命令终止,并返回结果集时,映射就开始了.适配器进入TableMappings集合中查找是否有与数据源相匹配的表/列名(默认表名:Table、Table1、Table2… ;默认列名与数据源中列名相同),如果有,就合并之,否则,就新建(默认情况下).当然,自动映射出的列所包含的架构信息非常有限,你需要通过设置AddWithKey/FillSchema来使它包含更多信息.
3. 填充数据:适配器在数据源和数据集中检索相匹配的主键列值,如果匹配,则合并之;否则,就新建.在填充数据表后,默认情况下,会自动将所有的行的状态设置为UnChanged.
提示:Fill会隐式的调用Open/Close方法.推荐手动显式调用.
Fill出错,会触发FillError事件,通常发生在需要损失数据精度的前提下,把数据从数据源转换到.NET Framework时.
Update方法总结
分自动更新和手动更新两种方式,更新后,行的状态会被置成UnChanged状态(默认情况下)
自动更新:只适合单表;需设置SelectCommand属性;降低性能,不推荐.
手动更新:需手动设置SelectCommand、InsertCommand、UpdateCommand、DeleteCommand;更新中会触发RowUpdating(检查数据源是否已被其它用户更新)事件和RowUpdated(更新出错后如果处理)事件.要避免增量冲突;可以有选择的更新(比如只更新RowState是delete的行).
如何为表定义主键
table.PrimaryKey = new DataColumn[](table.Columns["ProductID"]);
或者
DataColumn[] PriCol = new DataColumn[1];
PriCol[0] = table.Columns["ProductID"];
table.PrimaryKey = col;
定义多个主键
DataColumn[] PriCol = new DataColumn[2];
PriCol[0] = table.Columns["ProductID"];
PriCol[1] = table.Columns["ProductName"];
table.PrimaryKey = col;
如何添加约束
外键约束
ForeignKeyConstraint FatherChildFK = new ForeignKeyConstraint("FatherChildFK",
ds.Tables["Father"].Columns["FatherID"], ds.Tables["Child"].Columns["FatherID"]);
FatherChildFK.DeleteRule = Rule.None;
FatherChildFK.UpdateRule = Rule.Cascade;
ds.Tables["Child"].Constraints.Add(FatherChildFK);
列值唯一约束
UniqueConstraint unique = new UniqueConstraint(new DataColumn[] { table.Columns["ProductID"], table.Columns["ProductSign"] });
table.Constraints.Add(unique);
GridView
默认情况下,SQL Server的decimal数据类型允许最大有38个有效位,而.NET Framework的decimal最大只允许有28个有效位.怎么处理?
MS SQL Server各种数据类型,意义,作用,如何使用?
常见问题总结
级联删除,更新时应注意的问题
在用ado.net建立父子表时,可以设置关联为级联更新和级联删除等。但是在实际操作时要注意以下这种情况,比如 父表中做了删除操作,但还没有调用 AcceptChanges事件,这时子表中也会进行同样的工作,相联记录也是删除状态,实际父表中该记录还是存在的,只是行状态变成了删除,如果这时父表中调用了AcceptChanges事件,由于父表中该行被真正删除了。所以子表中这种具有删除状态的记录由于不符合联接要求,于是被子表强制删除了。而不是更新到数据库中。所以在调用Adapter.Update事件时,一定要先调用子表的。然后调用父表的。