一、概述
前面2篇文章,介紹了使用SqlCommand對象利用sql命令來操作數據庫。
這篇文章我們來介紹使用c#的DataSet 和 DataAdaper對象操作操作數據庫。
先來介紹下這兩個對象是干啥的。
1、DataSet對象
顧名思義,DataSet 可叫做數據集,可以簡單的理解為一個臨時數據庫,其中可包含多個表(DataTable),表中有記錄。
DataSet將從數據源(如數據庫)中獲得的數據保存在內存中,應用程序與內存中的DataSet進行交互,在這期間,不需要連接數據庫。
也就是說,一旦DataSet獲取數據後,就可以把數據庫鏈接關閉掉,這可以節約資源,提高數據的訪問和處理速度(因為是在內存中操作)。
當然,也是因為DataSet是在內存中,可以理解成一個緩存,數據放的越多,占系統內存越多,這個也是需要考慮的。
應用程序除了可以從DataSet中獲取數據,還可以更新DataSet中的數據,後面可以使用DataAdaper對象同步到數據源中,避免了額外的寫代碼處理,提高了代碼編寫的效率和質量。
2、DataAdaper對象
DataAdaper,可稱為數據適配器,它就是負責把DataSet和真實的數據源連接起來。本身DataSet對象是不直接和數據源打交道的,它只是個緩存,用戶存儲數據。
DataAdaper的工作機制是,它利用Connection對象連接數據庫,使用相關的Command對象封裝的命令(如sql語句)獲取數據,並把得到的數據填充到DataSet對象中。後續DataSet對象中的數據更新後需要同步到數據源中,也是由DataAdaper對象使用相關的Command對象來負責同步數據。
下面我們通過例子來說明如何使用。
操作的表 userinfo有兩個字段 username 和 password,其中username為主鍵。
二、查詢數據舉例
直接上例子代碼:
using System; using System.Data; using System.Data.SqlClient; using System.Windows.Forms; namespace DbExample { class DataSetExample { public void fetchData() { DataSet dataSet = new DataSet(); SqlDataAdapter adapter = new SqlDataAdapter(); SqlConnection conn = getConnection(); try { conn.Open(); SqlCommand command = new SqlCommand("select * from userinfo", conn); //綁定的sql命令 adapter.SelectCommand = command; adapter.Fill(dataSet, "userinfo"); //將數據獲取後裝載到dataSet中,當作一張表存儲在內存中 } catch (Exception ex) { MessageBox.Show(ex.Message, "查詢數據失敗"); } finally { conn.Close(); } showTable(dataSet); } private void showTable(DataSet dataSet) { DataTable table = dataSet.Tables["userinfo"]; //也可以通過序號獲取 int num = table.Rows.Count; MessageBox.Show(num.ToString()); //獲取表的記錄數 for (int i = 0; i < num; i++) { string re = table.Rows[i][0].ToString() + "," + table.Rows[i][1].ToString(); MessageBox.Show(re); } // 或者下面的方式遍歷更好 foreach (DataRow row in table.Rows) { string re = row[0].ToString() + "," + row[1].ToString(); MessageBox.Show(re); } //或者 DataRow[] rows = table.Select(); //可以通過參數控制輸出的內容,還可以排序 foreach (DataRow row in table.Rows) { string re = row[0].ToString() + "," + row[1].ToString(); MessageBox.Show(re); } } private SqlConnection getConnection() { string strConnection = @"Data Source = localhost\SQLEXPRESS; Initial Catalog = mydb; User Id = sa; Password = 12345678;"; SqlConnection conn = new SqlConnection(strConnection); return conn; } } }
從上面代碼中可以看出, showTable方法是在conn關閉後執行的,說明了一旦數據到了dataset中,就在內存中了,與數據庫是否保持連接無關了。
也可以看出,DataAdaper對象是利用綁定的command對象來實際獲取數據的,並利用自己的Fill方法將數據放到dataset對象中。
三、同步更新數據舉例(自動生成更新命令)
上面我們舉了個查詢的數據,沒有對dataset數據進行修改。下面例子會對dataset數據進行修改,然後同步到數據庫中。
我們先給出一個例子代碼:
using System; using System.Data; using System.Data.SqlClient; using System.Windows.Forms; namespace DbExample { class DataSetExample { public void fetchData() { DataSet dataSet = new DataSet(); SqlDataAdapter adapter = new SqlDataAdapter(); SqlConnection conn = getConnection(); try { conn.Open(); SqlCommand command = new SqlCommand("select * from userinfo", conn); //綁定的sql命令 adapter.SelectCommand = command; SqlCommandBuilder builder = new SqlCommandBuilder(adapter); adapter.Fill(dataSet, "userinfo"); //將數據獲取後裝載到dataSet中,當作一張表存儲在內存中 } catch (Exception ex) { MessageBox.Show(ex.Message, "操作失敗"); } finally { conn.Close(); } deleteRow(dataSet, adapter); MessageBox.Show("操作成功"); } private void deleteRow(DataSet dataSet, SqlDataAdapter adapter) { DataTable table = dataSet.Tables["userinfo"]; table.Rows[2].Delete(); //刪除第2條 adapter.Update(dataSet, "userinfo"); //同步到數據庫 } private SqlConnection getConnection() { string strConnection = @"Data Source = localhost\SQLEXPRESS; Initial Catalog = mydb; User Id = sa; Password = Xqh980234;"; SqlConnection conn = new SqlConnection(strConnection); return conn; } } }
與前面第一個查詢數據的例子相比,我們在fetchData方法的try代碼塊中增加了一行代碼
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
在fetchData方法的結尾將調用showTable 方法 改為 調用deleteRow方法。
執行上面代碼,我們會發現userinfo表中的第3條記錄被刪除了,但是代碼中沒看到任何顯示的 delete sql語句的執行。
下面我們來解釋下背後的原因。
在文章的開頭我們提到,DataSet對象只是個內存緩存,它是通過DataAapter對象來操作數據庫的,而DataAapter對象是通過其關聯的Command對象來具體實現數據庫操作的。
我們看到的代碼中的如下語句:
SqlCommand command = new SqlCommand("select * from userinfo", conn); //綁定的sql命令
adapter.SelectCommand = command;
這個DataAapter對象綁定了 SelectCommand 對象,這個Command通過其指定的查詢sql獲取數據後放到DataSet對象中。
對於查詢命令,還有一種更簡單的方式,用一個語句將上面兩條語句合一,如下:
adapter = new SqlDataAdapter("select * from userinfo", conn);
除了查詢外,還有DeleteCommand、InsertCommand、UpdateCommand 來完成更新操作。
但是在特定條件下,這三個Command不需要顯示的指定,只需要加上下面語句。
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
加上這個語句後,就會自動構建三個Command對象。
這就是為什麼上面的代碼沒看到有DeleteCommand對象,卻可以通過DataAapter來實現數據庫記錄的刪除操作。
需要說明的是,自動生成更新Command對象,需要滿足的條件是:
必須要綁定SelectCommand 對象,且指定的select語句只能包含單張表的操作,還必須返回至少一個主鍵或唯一列。
還需要注意的是:
要想同步數據庫,完成記錄的刪除,不能調用 table.Rows.Remove 或 Clear方法 ,這些方法會刪除DataSet中的數據,但DataAapter不是以這個為依據來判斷是否要刪除記錄,而是要調用DataRow對象的Delete方法,如上面的代碼:
table.Rows[2].Delete();
需要說明的是,調用了上面的Delete方法,在同步到數據庫之前,這條記錄在DataSet中還存在,但是卻不能訪問它,訪問會報錯。
但是在同步之後,該記錄將會從DataSet中被刪除。
而且要注意的是,在同步前,不能調用rows的Remove方法把該記錄從DataSet中刪除,這樣的話,同步時,記錄將不會從數據中刪除。
上面講的幾個注意點比較繞,需要弄清楚,並且寫代碼時要注意,否則很容易會導致數據紊亂,與預期的不一致。
上面我們看了刪除記錄的操作,下面我們看下增加記錄。
增加記錄比較簡單,沒有特別的注意點,代碼如下:
DataTable table = dataSet.Tables["userinfo"];
table.Rows.Add(new string[] { "x1","p1"});
table.Rows.Add(new string[] { "x2", "p2" });
adapter.Update(dataSet, "userinfo"); //同步到數據庫
上面代碼先往dataSet中插入2條記錄,再同步到數據庫。注意,無論是同步前,還是同步後,都能從dataSet中訪問到這兩條記錄。
我們再看如何修改記錄同步到數據庫。
DataTable table = dataSet.Tables["userinfo"];
table.Rows[2][0] = "x21";
table.Rows[2][1] = "kkk";
adapter.Update(dataSet, "userinfo"); //同步到數據庫
上面代碼修改dataSet中的第3條記錄的兩個字段,再同步到數據庫。注意,無論是同步前,還是同步後,都能從dataSet中訪問到這兩條記錄。
四、同步更新數據(非自動)
上面介紹了如何將修改後的DataSet數據同步到數據庫中,並且更新命令是自動生成的,下面介紹如何手工設置。
我們先看一個update的例子。
public void fetchDataEx() { DataSet dataSet = new DataSet(); SqlDataAdapter adapter = null; SqlConnection conn = getConnection(); try { conn.Open(); adapter = new SqlDataAdapter("select username,password from userinfo", conn); adapter.UpdateCommand = new SqlCommand( "update userinfo set password=@password " + "where username =@username", conn); adapter.UpdateCommand.Parameters.Add("@password", SqlDbType.VarChar, 50, "password"); adapter.UpdateCommand.Parameters.Add("@username", SqlDbType.VarChar, 50, "username"); adapter.Fill(dataSet, "userinfo"); } catch (Exception ex) { MessageBox.Show(ex.Message, "操作失敗"); } finally { conn.Close(); } deleteRow(dataSet, adapter); MessageBox.Show("操作成功"); } private void deleteRow(DataSet dataSet, SqlDataAdapter adapter) { DataTable table = dataSet.Tables["userinfo"]; table.Rows[2][1] = "y26"; adapter.Update(dataSet, "userinfo"); }
上面代碼,adapter顯示的綁定了一個UpdateCommand對象,其中包含了一個update語句。
需要注意的是,上面的update語句沒有更新主鍵username,如果要更新主鍵,則需要采用如下的寫法。
adapter.UpdateCommand = new SqlCommand( "update userinfo set username =@newusername,password=@password " + "where username =@username", conn); adapter.UpdateCommand.Parameters.Add("@password", SqlDbType.VarChar, 50, "password"); adapter.UpdateCommand.Parameters.Add("@newusername", SqlDbType.VarChar, 50, "username"); SqlParameter parameter = adapter.UpdateCommand.Parameters.Add("@username", SqlDbType.VarChar, 50, "username"); parameter.SourceVersion = DataRowVersion.Original; //更新DataSet的代碼 DataTable table = dataSet.Tables["userinfo"]; table.Rows[2][0] = "good2"; table.Rows[2][1] = "xxx12"; adapter.Update(dataSet, "userinfo"); //同步到數據庫
上面代碼可以看出,利用 parameter.SourceVersion = DataRowVersion.Original 將update語句中的where條件的username字段的取值設置為取原始值。
插入和刪除代碼的命令設置如下
adapter.DeleteCommand = new SqlCommand( "delete from userinfo where username =@username", conn); adapter.DeleteCommand.Parameters.Add("@username", SqlDbType.VarChar, 50, "username"); adapter.InsertCommand = new SqlCommand("insert into userinfo values(@username,@password)", conn); adapter.InsertCommand.Parameters.Add("@username", SqlDbType.VarChar, 50, "username"); adapter.InsertCommand.Parameters.Add("@password", SqlDbType.VarChar, 50, "password");
可以看出,如果是單表操作,最好還是采用上個例子講的自動生成更新command的方式,人工設置的方式還是比較麻煩的。
還有需要注意的時候,有時對DataSet做了多次操作,會導致對更新數據庫的順序(也就是說對執行 update ,delete,insert的順序)有要求。
這時需要顯示的指明同步的順序。這時可以使用 DataTable 的 Select 方法來返回僅引用具有特定 RowState 的 DataRow 數組。然後可以將返回的 DataRow 數組傳遞到 DataAdapter 的 Update 方法來處理已修改的行。通過指定要更新的行的子集,可以控制處理插入、更新和刪除的順序。如:
DataTable table = dataSet.Tables["userinfo"]; // First process deletes. adapter.Update(table.Select(null, null, DataViewRowState.Deleted)); // Next process updates. adapter.Update(table.Select(null, null, DataViewRowState.ModifiedCurrent)); // Finally, process inserts. adapter.Update(table.Select(null, null, DataViewRowState.Added));
本文介紹了,如何利用DataSet 和 DataAdaper對象來操作數據庫。這裡介紹的是單表操作。對於跨表的操作,會更為復雜,這在後面的文章中介紹。