2012年7月5日 星期四

C# 資料庫存取

首先關於資料庫的開啟可考慮兩種方式
1.用using{...}區塊
using(SqlConnection con = new SqlConnection())
{
    con.ConnectionString = "....";
    con.Open();
    ....
}

這種方法會在脫離區塊後立刻釋放Connection物件的資源
所以不用使用資料庫的關閉方法(如Close()、Dispose())
另外就是正規的寫法

2.

using System.Data.SqlClient;
....
SqlConnection con = new SqlConnection("xxx");       //xxx = connection string
SqlCommand cmd = new SqlCommand("xxx", con); //xxx = SQL command 
SqlDataReader dr;
...
con.Open();
dr = cmd.ExecuteReader();    //使用DataReader物件讀取資料庫內容
...
con.Close();  


SqlCommand物件的幾個常用方法跟屬性
   ExecuteNonQuery() -
   可以執行INSERT、UPDATE、DELETE等SQL command,成功時回傳受影響的紀錄筆數

   ExecuteScalar() -
   適用於結果集只回傳一個資料列的第一欄,例如SQL command 的COUNT()、MAX()等

   ExecuteReader() -
   執行Command物件中所指定的SQL的SELECT敘述,並建立一個DataReader來瀏覽資料

   CommandText -
   設定或取得要執行的命令


SqlCommand使用範例:

string sqlStr = string.Empty;
sqlStr = "INSERT INTO TableA(姓名, 聯絡方式, 薪水)" + "VALUES(@name, @tel, @pay)"
SqlCommand cmd = new SqlCommand( sqlStr , con);
cmd.CommandType = CommandType.StoreProcedure;     //預先編譯SQL處理函式

cmd.Parameters.Add(new SqlParameter("@name", SqlDbType.NVarChar));
cmd.Parameters.Add(new SqlParameter("@tel", SqlDbType.NVarChar));
cmd.Parameters.Add(new SqlParameter("@pay", SqlDbType.NVarChar));
cmd.Parameters["@name"].Value = "vc56";
cmd.Parameters["@tel"].Value = "0000000";
cmd.Parameters["@pay"].Value = "$20000";

cmd.ExecuteNonQuery();


DataReader有幾個方法跟屬性較為重要,以下列出:
   FieldCount屬性 -
   將已查詢欄位的總數傳回
 
   Read() -
   將指標移到下一筆,並判斷是否指到EOF
   如果到資料結尾則回傳false

   Item[i] -
   取得第i欄的資料內容

   Item["欄位名稱"]集合 -
   取得指定欄位名稱所指的資料內容

   GetValue(i) -
   取得第i欄位的資料內容,傳回值為object資料型別

   IsDBNull(i) -
   判斷第i個欄位使否為資料庫Null值

使用範例:
while(dr.Read())
{
  for(int i = 0; i < dr.FieldCount; i++)
  {
      textBox1.Text += dr[i].ToString() + "\t";
  }
}

以執行效率來說,使用索引會比使用欄位名稱還快
此外,取出時可使用方法來省略手動轉型的動作,如下:

while(dr.Read())
{
  textBox1.Text += dr.GetString(0) + "\t";
  textBox1.Text += dr.GetInt32(1).ToString() + "\t";
  ...
}


DataSet則是ADO.NET的特色
他會先將資料庫的內容存至記憶體中,並得以執行離線操作
操作完畢後再將內容回寫至資料庫中
適用於多用戶端資料存取,當然占用的記憶體多是不可避免的事
DataSet中可以包含一個以上的DataTable物件
由DataAdapter使用Command物件執行SQL command
再將取得的資料寫至DataSet,如此就能用DataTable存取資料表,範例:

using System.Data.SqlClient;
using System.Data;

.....
using(SqlConnection con = new SqlConnection())
{
    con.ConnectionString = "....";
    DataSet ds = new DataSet();
    SqlDataAdapter dsContent = new SqlDataAdapter("xxx", con);  //xxx = sql command
    dsContent.Fill(ds, "Content");    //使用Fill()時,DataAdapter會自動連線到資料庫
    /*
    Adapter也可以改成SqlDataAdapter dsContent = new SqlDataAdapter();
                                     dsContent.SelectCommand = cmd;
    這樣就可以使用SqlCommand物件來保持彈性


    另外有幾種特性語法示範
   int n = ds.Tables.Count;                          //取得DataSet中DataTable的總數
   String tName = ds.Table[i].TableName;  //取得第i個DataTable的表格名稱
   */
   DataTable dt = ds.Tables["Content"];     //對應SqlDataAdapter  Fill ()時填入的資料表名
   for(int i = 0; i < dt.Rows.Count; i++)
   {
        for(int j = 0; j < dt.Columns.Count; j++)
        {
              textBox1.Text += dt.Rows[i][j].ToString() + "\t";
        }
        textBox1.Text += Enviroment.NewLine;
   }
}
....


DataSet另外尚有使資料表之間產生關連的操作

ds.Realations.Add(  "關聯名稱",
      ds.Tables["dt1"].Columns["dt1要關聯的欄位名稱"],
      ds.Tables["dt2"].Columns["dt2要關聯的欄位名稱"],
)

日後可與DataView物件結合使用
dataGridView1.DataSource = ds;
dataGridView1.DataMember = "dt1";

dataGridView2.DataSource = ds;
dataGridView2.DataMember = "dt1.關聯名稱";

2012年7月3日 星期二

[轉貼] C# 淺析SqlConnection的dispose和close方法差異

原文

引用微軟ADO.Team的經理的話說,sqlconnection的close和dispose實際是做的同一件事,
唯一的區別是Dispose方法清空了connectionString,即設置為了null.

SqlConnection con = new SqlConnection("Data Source=localhost;Initial Catalog=northwind;User ID=sa;Password=steveg");
        con.Open();
        con.Close();
        con.Open();
        con.Dispose();
        con.Open();

    上例運行發現,close掉的connection可以重新open
    dispose的不行,因為connectionstring清空了,
    會拋出InvalidOperationException提示The ConnectionString property has not been initialized,
    但請注意此時sqlconnection對象還在。

    如果dispose後給connectionString重新賦值,則不會報錯。 
    由此得出的結論是不管是dispose還是close都不會銷毀對象,即不會釋放內存,
    它們會把sqlconnection對象丟到連接池中,那此對象什麼時候銷毀呢?
    我覺得應該是connection timeout設置的時間內,
    如果程序中沒有向連接池發出請求說要connection對象,sqlconnection對象便會銷毀,
    這也是連接池存在的意義。

    剛開始以為dispose會釋放資源清空內存,
    如果這樣的話,連接池不是每次都是要創建新對象,那何來重用connection呢?
    在網上看到很多人說close比dispose好,
    我想真正的原因是dispose後的sqlconnection對象要重新初始化連接字符串而已,
    並不是像某些人說的dispose會釋放對象。

    所以在try..catch和using的選擇上大膽的使用using吧,
    真正的效率差異我想可能只有百萬分之一秒吧
    (連接池重用該連接對象初始化連接字符串的時間),
    而且enterprise library中封裝的data access層全是用的using,
    從代碼的美觀和效率上綜合考慮,using好 

    補充:
    using不會捕捉其代碼快中的異常,
    只會最後執行dispose方法,相當於finally{dispose},
    本文主要是想說明dispose和close的差異,因為using是絕對dispose的,
    可是如果人為的寫try..finally有的人會選擇close有的人會選擇dispose,
    實際上在這2者的選擇上是有差異的。

    2012年7月1日 星期日

    C# 文字控制項-自動完成輸入功能

    C#的自動完成功能提供了當使用者在文字方塊中輸入的資料符合條件時
    便會自動填入或彈出符合的字串以提升使用者輸入資料的效率

    TextBox或ComboBox 中的三個屬性須注意:
    AutoCompleteSource、AutoCompleteMode、AutoCompleteCustomSource



    AutoCompleteSource的常用屬性值包括

    1.HistoryList - 以URL中的歷史清單作為自動完成輸入功能的來源
       ex:textBox1.AutoCompleteSource = AutoCompleteSource.HistoryList;
       程式碼部分以下類推

    2.RecentlyUsedList - 以URL中的歷史清單和最近瀏覽過的URL為來源

    3.AllUrl - 以最近瀏覽過的URL作為自動完成輸入功能的來源

    4.FileSystem - 以檔案系統為來源

    5.FileSystemDirectories - 以磁碟和目錄為來源,不包括檔案

    6.AllSystemSources - 以AllUrl和FileSystem為來源

    7.None - 取消自動完成輸入功能來源

    8.CustomSource - 自訂來源,須搭配AutoCompleteCustomSource屬性設定



    AutoCompleteMode常用屬性

    1.Append - 將最可能相符之候選字串其餘部分附加到現有字串之後,並反白顯示
       ex:textBox1.AutoCompleteMode = AutoCompleteMode.Append;

    2.Suggest - 挑選出最符合的字串於下拉選單的選項中

    3.SuggestAppend - 混用上述兩種功能

    4.None - 停用自動完成輸入功能


    AutoCompleteCustomSource屬性

    除了直接在Widget屬性介面上設計外
    也可以用程式碼部分撰寫,如:

    string[] content = new string[]{"a","b","c"};
    AutoCompleteStringCollection newAdd = new AutoCompleteStringCollection();
    newAdd.AddRange(content);

    textBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
    textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
    textBox1.AutoCompleteCustomSource = newAdd;