DataBase和DataSet同步數(shù)據(jù)
數(shù)據(jù)適配器概述
l DataAdapter 連接到數(shù)據(jù)庫(kù)以填充DataSet 的對(duì)象。然后,它又連接回?cái)?shù)據(jù)庫(kù),根據(jù)DataSet 保留數(shù)據(jù)時(shí)所執(zhí)行的操作來(lái)更新數(shù)據(jù)庫(kù)中的該數(shù)據(jù)。
l DataAdapter:它起著橋梁的作用,在DataSet 和其源數(shù)據(jù)存儲(chǔ)區(qū)之間進(jìn)行數(shù)據(jù)檢索和保存.
l DataAdapter對(duì)象可以隱藏和Connection、Command對(duì)象溝通的細(xì)節(jié),通過(guò)DataAdapter對(duì)象建立、初始化DataTable,從而和DataSet對(duì)象結(jié)合起來(lái)在內(nèi)存存放數(shù)據(jù)表副本,實(shí)現(xiàn)離線式數(shù)據(jù)庫(kù)操作.
數(shù)據(jù)適配器的屬性
l 數(shù)據(jù)源更新
– InsertCommand
– SelectCommand
– DeleteCommand
– UpdateCommand
l TableMappings:用于維持?jǐn)?shù)據(jù)集中的列和數(shù)據(jù)源中列的關(guān)系
l AcceptChangesDuringFill:決定AccepChnages方法是否被添加到數(shù)據(jù)集中的每一行所調(diào)用,默認(rèn)為true
TableMappings集合
l 數(shù)據(jù)集和數(shù)據(jù)源的橋梁:
– 數(shù)據(jù)集并不清除它所包含的數(shù)據(jù)從哪里來(lái),而Connection也不知道它所檢索的數(shù)據(jù)都發(fā)生了什么改變。
– 數(shù)據(jù)適配器用于維持這兩者之間的聯(lián)系,它通過(guò)TableMappings集合來(lái)實(shí)現(xiàn)這一目標(biāo)。
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","學(xué)生ID");
da.TableMappings[0].ColumnMappings.Add("StudentName","學(xué)生姓名");
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","成績(jī)");
這樣在DataSource中的列名就被映射為后者。
DataSet版本
DataSet中,可以存在同一個(gè)DataRow的多個(gè)版本。它們由DataRowWVersion枚舉來(lái)指定。
– Current該行中包含當(dāng)前值。
– Default根據(jù)當(dāng)前DataRowState,為默認(rèn)行版本。
– Original該行中包含其原始值。
– Proposed該行中包含建議值。
DataRowVersion
版本在以下情況下發(fā)生更改:
在調(diào)用DataRow 對(duì)象的BeginEdit 方法之后,如果更改該值,則Current 和Proposed 值變得可用。
在調(diào)用DataRow 對(duì)象的CancelEdit 方法之后,Proposed 值將被刪除。
在調(diào)用DataRow 對(duì)象的EndEdit 方法之后,Proposed值變成Current 值。
在調(diào)用DataRow 對(duì)象的AcceptChanges 方法之后,Original 值變得與Current 值相同。
在調(diào)用DataTable 對(duì)象的AcceptChanges 方法之后,Original 值變得與Current 值相同。
在調(diào)用DataRow 對(duì)象的RejectChanges 之后,Proposed 值將被丟棄,版本變成Current。
如果對(duì) DataSet、DataTable 或 DataRow 調(diào)用 AcceptChanges,則將使 DataRow 的所有 Original 值都將被重寫為該 DataRow 的 Current 值。如果已修改將該行標(biāo)識(shí)為唯一行的字段值,那么當(dāng)調(diào)用 AcceptChanges 后,Original 值將不再匹配數(shù)據(jù)源中的值。
Fill方法的使用
默認(rèn)情況下,在使用DataAdapter的Fill方法時(shí),除了會(huì)填充DataSet之外,還會(huì)自動(dòng)調(diào)用DataSet.AcceptChanges。調(diào)用后所有行狀態(tài)中沒(méi)有任何行是“新改變的”。
對(duì)于某些情況,如希望從多個(gè)數(shù)據(jù)源填充一個(gè)DataSet,再將其寫回另外一個(gè)數(shù)據(jù)存儲(chǔ),這時(shí)要把DataAdapter的屬性AcceptChangesDuringFill設(shè)置為false,以便讓結(jié)果行表現(xiàn)為新添加的行。
[參考代碼]
//將指定的行Fill到SqlDataAdapter中
//0表示要Fill的行標(biāo) 1表示要Fill的行數(shù)
da.Fill(ds,0,1,"AuthorAndTitle");
Update方法
當(dāng)調(diào)用Update 方法時(shí),DataAdapter 將分析已作出的更改并執(zhí)行相應(yīng)的命令(INSERT、UPDATE 或DELETE)。當(dāng)DataAdapter 遇到對(duì)DataRow 的更改時(shí),它將使用InsertCommand、UpdateCommand 或DeleteCommand 來(lái)處理該更改。這樣,您就可以通過(guò)在設(shè)計(jì)時(shí)指定命令語(yǔ)法并在可能時(shí)通過(guò)使用存儲(chǔ)過(guò)程來(lái)盡量提高ADO.NET 應(yīng)用程序的性能。
在調(diào)用Update 之前,必須顯式設(shè)置這些命令。如果調(diào)用了Update 但不存在用于特定更新的相應(yīng)命令(例如,不存在用于已刪除行的DeleteCommand),則將引發(fā)異常。
[參考代碼]
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";
//如果下面的不一致,說(shuō)明數(shù)據(jù)庫(kù)已經(jīng)更新了
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. 當(dāng)調(diào)用 Update 方法時(shí),DataAdapter 將分析已作出的更改并執(zhí)行相應(yīng)的命令(INSERT、UPDATE 或 DELETE)。當(dāng) DataAdapter 遇到對(duì) DataRow 的更改時(shí),它將使用 InsertCommand、UpdateCommand 或 DeleteCommand 來(lái)處理該更改。這樣,您就可以通過(guò)在設(shè)計(jì)時(shí)指定命令語(yǔ)法并在可能時(shí)通過(guò)使用存儲(chǔ)過(guò)程來(lái)盡量提高 ADO.NET 應(yīng)用程序的性能。在調(diào)用 Update 之前,必須顯式設(shè)置這些命令。如果調(diào)用了 Update 但不存在用于特定更新的相應(yīng)命令(例如,不存在用于已刪除行的 DeleteCommand),則將引發(fā)異常。
2. 請(qǐng)注意,在 UPDATE 語(yǔ)句的 WHERE 子句中指定的參數(shù)設(shè)置為使用 SourceColumn 的 Original 值。這一點(diǎn)很重要,因?yàn)?Current 值可能已被修改,并且可能不匹配數(shù)據(jù)源中的值。Original 值是曾用來(lái)從數(shù)據(jù)源填充 DataTable 的值。
3. 如果 SelectCommand 返回 OUTER JOIN 的結(jié)果,則 DataAdapter 不會(huì)為生成的 DataTable 設(shè)置 PrimaryKey 值。您必須自己定義 PrimaryKey 以確保正確解析重復(fù)行
CommandBuilder
如果DataTable 映射到單個(gè)數(shù)據(jù)庫(kù)表或從單個(gè)數(shù)據(jù)庫(kù)表生成,則可以利用CommandBuilder 對(duì)象自動(dòng)生成DataAdapter 的DeleteCommand、InsertCommand 和UpdateCommand。
滿足以下條件,就可以使用CommandBuilder自動(dòng)生成命令
(1) DataTable 映射到單個(gè)數(shù)據(jù)庫(kù)表或從單個(gè)數(shù)據(jù)庫(kù)表生成
(2) 必須使用了SelectCommand命令了,并有主建
為了生成INSERT、UPDATE 或DELETE語(yǔ)句,CommandBuilder 會(huì)自動(dòng)使用SelectCommand 屬性來(lái)檢索所需的元數(shù)據(jù)集。
[參考代碼]
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");
數(shù)據(jù)適配器的事件
OnRowUpdating:在數(shù)據(jù)行更新前執(zhí)行
OnRowUpdated:在數(shù)據(jù)行更新后執(zhí)行。其最佳用法是檢查單條更新語(yǔ)句的執(zhí)行結(jié)果。
SqlRowUpdatedEventArgs屬性
屬性
描述
Command
要執(zhí)行的數(shù)據(jù)命令
Errors
錯(cuò)誤
Row
要更新的行
StatementType
要執(zhí)行的命令類型,可能為Select、Insert、Delete和Update
RecordsAffected
要影響的行數(shù)
TableMapping
更新所使用的DataTableMapping
[參考代碼]
myDataAdapter.RowUpdating += new SqlRowUpdatingEventHandler(MyUpdatingHandler);
//省略,OnRowUpdating主要用于在更新前判斷數(shù)據(jù)源是否已經(jīng)被其他客戶端更改
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("出錯(cuò)!有用戶已經(jīng)修改過(guò)數(shù)據(jù)集!");
e.Status = UpdateStatus.ErrorsOccurred;//報(bào)錯(cuò)
}
myConnection.Close();
break;
}
}
}
myDataAdapter.RowUpdated += new SqlRowUpdatedEventHandler(MyUpdatedHandler);
//省略,OnRowUpdated主要用于更新出錯(cuò)時(shí),是否忽略錯(cuò)誤繼續(xù)更新
public void MyUpdatedHandler(object adapter,SqlRowUpdatedEventArgs e)
{
switch(e.StatementType)
{
case StatementType.Update:
if(e.Status==UpdateStatus.ErrorsOccurred)
{
e.Status = UpdateStatus.SkipCurrentRow;
break;
}
}
}
使用數(shù)據(jù)適配器最佳實(shí)踐
使用多個(gè)表填充一個(gè)DataSet
如果你是用批處理從多個(gè)表返回?cái)?shù)據(jù)并把這些數(shù)據(jù)填充到一個(gè)DataSet,fill方法將會(huì)使用第一個(gè)表的表名命名第一個(gè)表,以后的表命名將會(huì)采用在第一個(gè)表的表名基礎(chǔ)上加上一個(gè)遞增的數(shù)字。
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM Customers;SELECT * FROM Orders;", myConnection);
da.TableMappings.Add("Customers1", "Orders");
DataSet ds = new DataSet();
da.Fill(ds, "Customers");
避免自動(dòng)增量值沖突
DataSet 使您可標(biāo)識(shí)那些添加新行時(shí)自動(dòng)對(duì)其值進(jìn)行遞增的列。在DataSet 中使用自動(dòng)增量的列時(shí),如果自動(dòng)增量的列來(lái)自數(shù)據(jù)源,可避免添加到DataSet 的行和添加到數(shù)據(jù)源的行之間本地編號(hào)沖突。
例如一個(gè)表,它的主鍵列CustomerID 是自動(dòng)增量的。兩個(gè)新的客戶信息行添加到表中,并接收到自動(dòng)增量的CustomerID 值1 和2。然后,只有第二個(gè)客戶行被傳遞給DataAdapter 的方法Update,新添加的行在數(shù)據(jù)源接收到一個(gè)自動(dòng)增量的CustomerID 值1,與DataSet 中的值2 不匹配。當(dāng)DataAdapter 用返回值填充表中第二行時(shí),就會(huì)出現(xiàn)約束沖突,因?yàn)榈谝粋€(gè)客戶行已經(jīng)使用了CustomerID 值1。
要避免這種情況,建議把DataSet 中的列創(chuàng)建為AutoIncrementStep 值等于-1 并且AutoIncrementSeed 值等于0,另外,還要確保數(shù)據(jù)源生
成的自動(dòng)增量標(biāo)識(shí)值從1 開(kāi)始,并且以正階值遞增。
CommandBuilder 的使用
在設(shè)計(jì)階段不要使用CommandBuilder,否者產(chǎn)生DataAdapter Command屬性的進(jìn)程將會(huì)受到干擾。? CommandBuilder使用SelectCommand決定其他Command屬性的值。如果DataAdapter的SelectCommand本身發(fā)生變化,應(yīng)該使用RefreshSchema去刷新Command的屬性。
只要DataAdapter的Command屬性為空,CommandBuilder就僅僅創(chuàng)建一個(gè)Command,即使你明確地指定Command的屬性值,CommandBuilder也不會(huì)重寫,所以如果你想創(chuàng)建一個(gè)Command并保留以前的屬性設(shè)置,那么就把Command的屬性設(shè)置為null。
QA
Q: 適配器事件的例子,應(yīng)該是更新前查看是否有其他的用戶已修改了"數(shù)據(jù)庫(kù)",而不是"數(shù)據(jù)集"吧?
A:對(duì),是數(shù)據(jù)庫(kù)。
Q:在智能客戶端中,有多個(gè)客戶端對(duì)服務(wù)器端上的數(shù)據(jù)進(jìn)行增、刪、改操作,客戶端可以脫機(jī)進(jìn)行操作,通過(guò)計(jì)時(shí)器與服務(wù)器同步數(shù)據(jù) 在一個(gè)客戶端上怎樣把別的客戶端刪除的記錄同步到本地呢?
A:建議把客戶端用戶對(duì)服務(wù)器數(shù)據(jù)庫(kù)中的刪除不做真正刪除,只是做刪除標(biāo)記。這樣就可以把它同步到本地了。
Q: 在更新表時(shí)出錯(cuò),是否是因?yàn)楸桓碌谋頉](méi)有設(shè)置主鍵?
A:對(duì)。通常情況如果沒(méi)有設(shè)置主鍵,會(huì)更新失敗。
Q: DataSet有多行(n行)需要被更新時(shí),調(diào)用DataAdapter的Update方法后,RowUpdated事件,是觸發(fā)一次,還是觸發(fā)多次(n次)?
A:觸發(fā)n次,每更新一行都觸發(fā)。
Q: DataAdapter中的Command似乎不是很靈活(我是指只能寫死在代碼嗎?),我是想,能不能通過(guò)配置文件來(lái)?這樣會(huì)比較靈活!
A:可以把他寫到XML文件中,讀入XML文件,得到對(duì)應(yīng)的Command,也是可以的。
聯(lián)系客服