DataBase和DataSet同步数据
数据适配器概述
l DataAdapter 连接到数据库以填充DataSet 的对象。然后,它又连接回数据库,根据DataSet 保留数据时所执行的操作来更新数据库中的该数据。
l DataAdapter:它起着桥梁的作用,在DataSet 和其源数据存储区之间进行数据检索和保存.
l DataAdapter对象可以隐藏和Connection、Command对象沟通的细节,通过DataAdapter对象建立、初始化DataTable,从而和DataSet对象结合起来在内存存放数据表副本,实现离线式数据库操作.
数据适配器的属性
l 数据源更新
– InsertCommand
– SelectCommand
– DeleteCommand
– UpdateCommand
l TableMappings:用于维持数据集中的列和数据源中列的关系
l AcceptChangesDuringFill:决定AccepChnages方法是否被添加到数据集中的每一行所调用,默认为true
TableMappings集合
l 数据集和数据源的桥梁:
– 数据集并不清除它所包含的数据从哪里来,而Connection也不知道它所检索的数据都发生了什么改变。
– 数据适配器用于维持这两者之间的联系,它通过TableMappings集合来实现这一目标。
l 包括
– DataTableMappings
– DataColumnMappings
1. SourceColumn
2. DataSetColumn
[参考代码]
protected System.Web.UI.WebControls.DropDownList ddlClassCode;
//省略
SqlDataAdapter da = new SqlDataAdapter("select Distinct classCode from tbClassInfo",con);
DataSet ds = new DataSet("myDs");
da.Fill(ds,"Class");
ddlClassCode.DataTextField = "ClassCode";
ddlClassCode.DataSource = ds.Tables["Class"].DefaultView;
ddlClassCode.DataBind();
ddlClassCode.SelectedIndex = nIndex;
string strSelectedClass = ddlClassCode.SelectedItem.Text;
string strSql = "select * from tbStudentInfo where StudentID in (select studentid from tbClassInfo where classcode='"+strSelectedClass+"')";
da.SelectCommand.CommandText =strSql;
//映射
da.TableMappings.Add("tbStudentInfo","Student");
da.TableMappings[0].ColumnMappings.Add("StudentID","学生ID");
da.TableMappings[0].ColumnMappings.Add("StudentName","学生姓名");
da.TableMappings[0].ColumnMappings.Add("StudentPass","密码");
da.TableMappings[0].ColumnMappings.Add("Sex","性别");
da.TableMappings[0].ColumnMappings.Add("BirthDay","生日");
da.TableMappings[0].ColumnMappings.Add("Email","邮件地址");
da.TableMappings[0].ColumnMappings.Add("Score","成绩");
这样在DataSource中的列名就被映射为后者。
DataSet版本
• DataSet中,可以存在同一个DataRow的多个版本。它们由DataRowWVersion枚举来指定。
– Current该行中包含当前值。
– Default根据当前DataRowState,为默认行版本。
– Original该行中包含其原始值。
– Proposed该行中包含建议值。
DataRowVersion
版本在以下情况下发生更改:
• 在调用DataRow 对象的BeginEdit 方法之后,如果更改该值,则Current 和Proposed 值变得可用。
• 在调用DataRow 对象的CancelEdit 方法之后,Proposed 值将被删除。
• 在调用DataRow 对象的EndEdit 方法之后,Proposed值变成Current 值。
• 在调用DataRow 对象的AcceptChanges 方法之后,Original 值变得与Current 值相同。
• 在调用DataTable 对象的AcceptChanges 方法之后,Original 值变得与Current 值相同。
• 在调用DataRow 对象的RejectChanges 之后,Proposed 值将被丢弃,版本变成Current。
如果对 DataSet、DataTable 或 DataRow 调用 AcceptChanges,则将使 DataRow 的所有 Original 值都将被重写为该 DataRow 的 Current 值。如果已修改将该行标识为唯一行的字段值,那么当调用 AcceptChanges 后,Original 值将不再匹配数据源中的值。
Fill方法的使用
• 默认情况下,在使用DataAdapter的Fill方法时,除了会填充DataSet之外,还会自动调用DataSet.AcceptChanges。调用后所有行状态中没有任何行是“新改变的”。
• 对于某些情况,如希望从多个数据源填充一个DataSet,再将其写回另外一个数据存储,这时要把DataAdapter的属性AcceptChangesDuringFill设置为false,以便让结果行表现为新添加的行。
[参考代码]
//将指定的行Fill到SqlDataAdapter中
//0表示要Fill的行标 1表示要Fill的行数
da.Fill(ds,0,1,"AuthorAndTitle");
Update方法
• 当调用Update 方法时,DataAdapter 将分析已作出的更改并执行相应的命令(INSERT、UPDATE 或DELETE)。当DataAdapter 遇到对DataRow 的更改时,它将使用InsertCommand、UpdateCommand 或DeleteCommand 来处理该更改。这样,您就可以通过在设计时指定命令语法并在可能时通过使用存储过程来尽量提高ADO.NET 应用程序的性能。
• 在调用Update 之前,必须显式设置这些命令。如果调用了Update 但不存在用于特定更新的相应命令(例如,不存在用于已删除行的DeleteCommand),则将引发异常。
[参考代码]
SqlDataAdapter catDA = new SqlDataAdapter("SELECT * FROM Categories", nwindConn);
catDA.UpdateCommand = new SqlCommand("UPDATE Categories SET CategoryName = @CategoryName " +"WHERE CategoryID = @CategoryID" , nwindConn);
catDA.UpdateCommand.Parameters.Add("@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");
SqlParameter workParm = catDA.UpdateCommand.Parameters.Add("@CategoryID", SqlDbType.Int);
workParm.SourceColumn = "CategoryID";
//如果下面的不一致,说明数据库已经更新了
workParm.SourceVersion = DataRowVersion.Original;
DataSet catDS = new DataSet();
catDA.Fill(catDS, "Categories");
DataRow cRow = catDS.Tables["Categories"].Rows[0];
cRow["CategoryName"] = "NewName";
catDA.Update(catDS,"Categories");
MSDN上面的解释:
1. 当调用 Update 方法时,DataAdapter 将分析已作出的更改并执行相应的命令(INSERT、UPDATE 或 DELETE)。当 DataAdapter 遇到对 DataRow 的更改时,它将使用 InsertCommand、UpdateCommand 或 DeleteCommand 来处理该更改。这样,您就可以通过在设计时指定命令语法并在可能时通过使用存储过程来尽量提高 ADO.NET 应用程序的性能。在调用 Update 之前,必须显式设置这些命令。如果调用了 Update 但不存在用于特定更新的相应命令(例如,不存在用于已删除行的 DeleteCommand),则将引发异常。
2. 请注意,在 UPDATE 语句的 WHERE 子句中指定的参数设置为使用 SourceColumn 的 Original 值。这一点很重要,因为 Current 值可能已被修改,并且可能不匹配数据源中的值。Original 值是曾用来从数据源填充 DataTable 的值。
3. 如果 SelectCommand 返回 OUTER JOIN 的结果,则 DataAdapter 不会为生成的 DataTable 设置 PrimaryKey 值。您必须自己定义 PrimaryKey 以确保正确解析重复行
CommandBuilder
• 如果DataTable 映射到单个数据库表或从单个数据库表生成,则可以利用CommandBuilder 对象自动生成DataAdapter 的DeleteCommand、InsertCommand 和UpdateCommand。
满足以下条件,就可以使用CommandBuilder自动生成命令
(1) DataTable 映射到单个数据库表或从单个数据库表生成
(2) 必须使用了SelectCommand命令了,并有主建
• 为了生成INSERT、UPDATE 或DELETE语句,CommandBuilder 会自动使用SelectCommand 属性来检索所需的元数据集。
[参考代码]
SqlDataAdapter catDA = new SqlDataAdapter("SELECT * FROM Categories", nwindConn);
catDA.InsertCommand = new SqlCommand("Insert into Categories(CategoryName,Description) values"+" (@CategoryName,@Description)", nwindConn);
catDA.InsertCommand.Parameters.Add("@CategoryName", SqlDbType.NVarChar, 15, "CategoryName");
catDA.InsertCommand.Parameters.Add("@Description", SqlDbType.NText, 16, "Description");
DataSet catDS = new DataSet();
catDA.Fill(catDS, "Categories");
DataRow dr = catDS.Tables["Categories"].NewRow();
dr["CategoryName"] = "Added New Name";
dr["Description"] = "my Description";
catDS.Tables["Categories"].Rows.Add(dr);
catDA.Update(catDS,"Categories");
数据适配器的事件
• OnRowUpdating:在数据行更新前执行
• OnRowUpdated:在数据行更新后执行。其最佳用法是检查单条更新语句的执行结果。
SqlRowUpdatedEventArgs属性
属性
描述
Command
要执行的数据命令
Errors
错误
Row
要更新的行
StatementType
要执行的命令类型,可能为Select、Insert、Delete和Update
RecordsAffected
要影响的行数
TableMapping
更新所使用的DataTableMapping
[参考代码]
myDataAdapter.RowUpdating += new SqlRowUpdatingEventHandler(MyUpdatingHandler);
//省略,OnRowUpdating主要用于在更新前判断数据源是否已经被其他客户端更改
public void MyUpdatingHandler(object adapter,SqlRowUpdatingEventArgs e)
{
switch(e.StatementType)
{
case StatementType.Update:
{
SqlConnection myConnection = new SqlConnection( "server=(local);uid=sa;pwd=111;database=Pubs" );
string strSql = "Select * From Authors where au_fname='"
+e.Row["au_fname",DataRowVersion.Original]+"'";
SqlCommand com = new SqlCommand(strSql,myConnection);
myConnection.Open();
if(com.ExecuteNonQuery()==0)
{
Response.Write("出错!有用户已经修改过数据集!");
e.Status = UpdateStatus.ErrorsOccurred;//报错
}
myConnection.Close();
break;
}
}
}
myDataAdapter.RowUpdated += new SqlRowUpdatedEventHandler(MyUpdatedHandler);
//省略,OnRowUpdated主要用于更新出错时,是否忽略错误继续更新
public void MyUpdatedHandler(object adapter,SqlRowUpdatedEventArgs e)
{
switch(e.StatementType)
{
case StatementType.Update:
if(e.Status==UpdateStatus.ErrorsOccurred)
{
e.Status = UpdateStatus.SkipCurrentRow;
break;
}
}
}
使用数据适配器最佳实践
使用多个表填充一个DataSet
• 如果你是用批处理从多个表返回数据并把这些数据填充到一个DataSet,fill方法将会使用第一个表的表名命名第一个表,以后的表命名将会采用在第一个表的表名基础上加上一个递增的数字。
• SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Customers;SELECT * FROM Orders;", myConnection);
• da.TableMappings.Add("Customers1", "Orders");
• DataSet ds = new DataSet();
• da.Fill(ds, "Customers");
避免自动增量值冲突
• DataSet 使您可标识那些添加新行时自动对其值进行递增的列。在DataSet 中使用自动增量的列时,如果自动增量的列来自数据源,可避免添加到DataSet 的行和添加到数据源的行之间本地编号冲突。
• 例如一个表,它的主键列CustomerID 是自动增量的。两个新的客户信息行添加到表中,并接收到自动增量的CustomerID 值1 和2。然后,只有第二个客户行被传递给DataAdapter 的方法Update,新添加的行在数据源接收到一个自动增量的CustomerID 值1,与DataSet 中的值2 不匹配。当DataAdapter 用返回值填充表中第二行时,就会出现约束冲突,因为第一个客户行已经使用了CustomerID 值1。
• 要避免这种情况,建议把DataSet 中的列创建为AutoIncrementStep 值等于-1 并且AutoIncrementSeed 值等于0,另外,还要确保数据源生
成的自动增量标识值从1 开始,并且以正阶值递增。
CommandBuilder 的使用
• 在设计阶段不要使用CommandBuilder,否者产生DataAdapter Command属性的进程将会受到干扰。• CommandBuilder使用SelectCommand决定其他Command属性的值。如果DataAdapter的SelectCommand本身发生变化,应该使用RefreshSchema去刷新Command的属性。
• 只要DataAdapter的Command属性为空,CommandBuilder就仅仅创建一个Command,即使你明确地指定Command的属性值,CommandBuilder也不会重写,所以如果你想创建一个Command并保留以前的属性设置,那么就把Command的属性设置为null。
QA
Q: 适配器事件的例子,应该是更新前查看是否有其他的用户已修改了"数据库",而不是"数据集"吧?
A:对,是数据库。
Q:在智能客户端中,有多个客户端对服务器端上的数据进行增、删、改操作,客户端可以脱机进行操作,通过计时器与服务器同步数据 在一个客户端上怎样把别的客户端删除的记录同步到本地呢?
A:建议把客户端用户对服务器数据库中的删除不做真正删除,只是做删除标记。这样就可以把它同步到本地了。
Q: 在更新表时出错,是否是因为被更新的表没有设置主键?
A:对。通常情况如果没有设置主键,会更新失败。
Q: DataSet有多行(n行)需要被更新时,调用DataAdapter的Update方法后,RowUpdated事件,是触发一次,还是触发多次(n次)?
A:触发n次,每更新一行都触发。
Q: DataAdapter中的Command似乎不是很灵活(我是指只能写死在代码吗?),我是想,能不能通过配置文件来?这样会比较灵活!
A:可以把他写到XML文件中,读入XML文件,得到对应的Command,也是可以的。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lne818/archive/2006/07/10/901254.aspx