2012年9月16日 星期日

使用 ADO.NET Connected 類別

DBConnection

  • 與 Connecton 相關的物件,最主要的功能就是移動「Disconnected 相關物件」與「資料儲存區」間的資料。
  • 不同的資料儲存區,會使用不同的資料提供者,如 SqlClientDataProvider 、 OleDbDataProvider 、 OracleClientDataProvider 。
  • 要建立一個連線,必須要有一個有效的連線字串。
  • 傳送指令到資料儲存區前,必須開啟連線。
  • 使用 Using 區塊可以在程式碼結束時,自動處置連線。
  • 若使用同一個連線字串建立連線時,系統會使用 ThreadPool 中的連線物件,減少另外建立連線的資源消耗。
protected SqlConnection CreateConnection()
{
    SqlConnection connection = new SqlConnection();
    connection.ConnectionString =
        @"
        Data Source = VITO-2011W7\W7_SQLSVR_2008;
        Initial Catalog = TestDB; 
        User ID=vito; 
        Password=vito1234;
        ";
    return connection;
}

protected void btnCreateConnection_Click(object sender, EventArgs e)
{
    SqlConnection connection = CreateConnection();
    try
    {
        connection.Open();
    }
    catch (Exception ex)
    {
        myDebug.Write(ex.ToString());
    }
    finally
    {
        connection.Close();
    }
}

DbCommand

  • DbCommand 可以傳送一個或多個結構化的查詢語言。
  • DbCommand 必須透過一個開啟的連線,才能連結到資料儲存區。
  • DbCommand 必須指明 CommandText 及 CommandType 屬性, CommandType 屬性預設為 Text 。
  • DbCommand 可以透過 DbParameter 物件傳遞參數資料。

建立 DbCommand

SqlCommand oCmd = oConn.CreateCommand();
oCmd.CommandType = CommandType.Text;
oCmd.CommandText = "SELECT top 100 * From tblUser";

執行 DbCommand

  • ExecuteNonQuery :執行 SQL 陳述式,不傳回值。
  • ExecuteScalar :執行 SQL查詢陳述式,並傳回查詢結果集中第一個資料列的第一個資料行。
  • ExecuteReader :執行 SQL 陳述式,並傳回 DbDataReader。
using (SqlConnection oConn = CreateTestDBConnection())
{
    oConn.Open();

    //===========================================================
    // ExecuteNonQuery
    //===========================================================
    DbCommandoCmd1 = oConn.CreateCommand();
    oCmd1.CommandType = CommandType.Text;
    oCmd1.CommandText = "Insert Employee(Name,Salary) Values ('vito','1000')";
    oCmd1.ExecuteNonQuery();

    //===========================================================
    // ExecuteScalar
    //===========================================================
    DbCommandoCmd2 = oConn.CreateCommand();
    oCmd2.CommandType = CommandType.StoredProcedure;
    oCmd2.CommandText = "usp_InsertEmployee";
    int id = Convert.ToInt32( oCmd2.ExecuteScalar() );

    //===========================================================
    // ExecuteReader
    //===========================================================
    DbCommandoCmd3 = oConn.CreateCommand();
    oCmd3.CommandType = CommandType.Text;
    oCmd3.CommandText = "SELECT top 100 * From tblUser";
    DbDataReader oReader = oCmd3.ExecuteReader();
}
}

使用 DbDataReader 物件

  • 表示伺服器端(server-side)資料指標。
  • 必須由 ExecuteReader 回傳,不能自已建立。
  • 只能順向唯讀。(forward-only & read-only)
  • 透過 Read 方法,可以擷取資料至緩衝區。但一次僅能處理一個資料列。
  • 使用 DataReader 時,相關聯的 Connection 會忙於服務 DataReader,所以無法在相同 Connection 上再執行其他作業,直到 DataReader 關閉為止。
SqlCommand oCmd = oConn.CreateCommand();
oCmd.CommandType = CommandType.Text;
oCmd.CommandText = "SELECT top 10000 * From tblUser";

oCmd.Connection = oConn;
oConn.Open();
oReader = oCmd.ExecuteReader();

//順向逐筆讀取
while (oReader.Read())
{
    if (oReader["UserName"].ToString() == "vito")
    {
        myDebug.WriteLine("Find");
        break;
    }
}

//將 DataReader 轉成 DataTable 
DataTable dTable = new DataTable();
dTable.Load(oReader);

使用多重作用結果集 ( Multiple Active Result Sets , MARS )

  • 通常在建立伺服器指標之後,若這個指標沒有關閉,在同一個連線之下,就無法再執行任何 Command 。
  • 也就是若建立的 SqlDataReader 沒有關閉前,就無法再對資料庫下命令。
  • 不過,從 Server2005 之後,開始支援一個連線多個指令,搭配連線字串的設定,即可啟用就 MARS。
  • 但是 MARS 僅能簡化設計的工作量,並不能增加系統效能,反而會增加系統的負載量,所以請勿隨意使用。
protected SqlConnection CreateMARSConnection()
{
    SqlConnection connection = new SqlConnection();
    connection.ConnectionString =
        @"
        Data Source = VITO-2011W7\W7_SQLSVR_2008;
        Initial Catalog = TestDB; 
        User ID = vito; 
        Password = vito1234;
        MultipleActiveResultSets = True;
        ";
    return connection;
}
protected void btnMARS_Click(object sender, EventArgs e)
{
    string UserID = "";
    string UserName = "";
    using (SqlConnection oConn = CreateMARSConnection())
    {
        oConn.Open();

        SqlCommand oCmd1 = new SqlCommand("SELECT top 10 UserID From tblUser", oConn);
        SqlDataReader Reader1 = oCmd1.ExecuteReader();

        SqlCommand oCmd2 = new SqlCommand();
        oCmd2.Connection = oConn;
        oCmd2.CommandText = "SELECT UserName From tblUser Where UserID = @UserID";

        while (Reader1.Read())
        {
            UserID = Reader1["UserID"].ToString();
            oCmd2.Parameters.Clear();
            oCmd2.Parameters.Add(new SqlParameter("@UserID", UserID));
            UserName = oCmd2.ExecuteScalar().ToString();
            //若沒有啟用 MARS ,會產生「已經開啟一個與這個 Command 相關的 DataReader,必須先將它關閉。」
        }
    }
}

使用 SqlBulkCopy 物件執行大量複製作業

SqlBulkCopy 這個類別提供多個 WriteToServer 多載方法,分別是針對不同型態的資料進行複製作業。它是一個高效率的資料匯入方法,但匯入對象僅限 SQL Server 。

public void WriteToServer(DataRow[] rows);
public void WriteToServer(DataTable table);
public void WriteToServer(IDataReader reader);
public void WriteToServer(DataTable table, DataRowState rowState);
protected void btnSqlBulkCopy_Click(object sender, EventArgs e)
{
    using (SqlConnection oConn = CreateTestDBConnection())
    {
        oConn.Open();

        SqlCommand oCmd = oConn.CreateCommand();
        oCmd.CommandType = CommandType.Text;
        oCmd.CommandText = "SELECT top 10000 UserName From tblUser";

        //建立目標資料庫的連線
        SqlConnection bulkCopyConnection = CreateTestDBConnection();
        bulkCopyConnection.Open();

        //執行 BulkCopy
        SqlDataReader rdr = oCmd.ExecuteReader();
        SqlBulkCopy bc = new SqlBulkCopy(bulkCopyConnection);
        bc.DestinationTableName = "tblUserName";
        bc.WriteToServer(rdr);

        //關閉連線
        bulkCopyConnection.Close();
    }
}

DbDataAdapter

若要執行 SQL 指令,你只要建立 DbCommand 物件即可。 若想取得執行結果,也可以叫用 DbCommandExecuteReader 方法, 但這個方法回傳的資料是一個 DbDataReader 物件,它是一個「連線狀態」的物件,也就是在讀取資料時,必須與資料來源一直保持連接的狀態。

若想要更進一步的操作資料庫,就必須藉由 DataAdapter 來幫助。 DataAdapter 是由一個 Connection 和數個 SQL Commnad 所組合成的物件, DataAdapter 是採用離線存取的方式, 在設定好想關資訊後,就可以透過 Fill 方法去取得資料,它會自動開啟連線,並且在撈完資料後關閉連線。 你可以藉由它將查詢結果填入 DataSetDataTable 等離線狀態的物件。 也可以利用它將異動後的資料更新回資料庫去。

SqlDataAdapter

建構子

public SqlDataAdapter();
public SqlDataAdapter(SqlCommand selectCommand);
public SqlDataAdapter(string selectCommandText, SqlConnection selectConnection);
public SqlDataAdapter(string selectCommandText, string selectConnectionString);

屬性

DbDataAdapter 物件中包含了 SelectCommand、InsertCommand、UpdateCommand、DeleteCommand 四種屬性,用來擷取或更新資料庫由的資料。

  • SelectCommand :a command used to select records in the data source.
  • InsertCommand :a command used to insert new records into the data source.
  • UpdateCommand :a command used to insert update records into the data source.
  • DeleteCommand :a command used to insert delete records into the data source.
  • UpdateBatchSize :啟用或停用批次處理。
    • 1:停用批次。此為預設值,將變更的資料列一列一列的回傳處理。
    • 0:沒有限制批次大小。
    • n:每次以 n 筆資料批次傳送處理。 (n>1)

Fill 方法

Fill 方法是用來將資料由資料庫移到指定的物件。 它會依據 DbDataAdapter 中的 SelectCommand 中的指令,將資料是由資料庫載入到 DataTable 之中。

public override int Fill(DataSet dataSet);
public int Fill(DataTable dataTable);
public int Fill(DataSet dataSet, string srcTable);
protected virtual int Fill(DataTable dataTable, IDbCommandcommand, CommandBehavior behavior);
......
protected void btnFill_Click(object sender, EventArgs e)
{
    using (SqlConnection connection = CreateTestDBConnection())
    {
        connection.Open();

        SqlCommand cmd = connection.CreateCommand();
        cmd.CommandText = "SELECT UserID, UserName FROM tblUser";

        SqlDataAdapter adapter = new SqlDataAdapter(cmd);
        DataSet dsUsers = new DataSet("Users");
        adapter.Fill(dsUsers, "Users");  //將查詢結果填到DataSet

        Tools.bindDataToGridView(gv1, dsUsers, "Users");
    }
}

Update 方法

public int Update(DataRow[] dataRows);
public override int Update(DataSet dataSet);
public int Update(DataTable dataTable);
protected virtual int Update(DataRow[] dataRows, DataTableMapping tableMapping);
public int Update(DataSet dataSet, string srcTable);

Update 方法是用來將變更的資料回存到資料庫。 它會依據 DataRow 的狀態值判斷,只要 DataRow.RowState 值不是 Unchanged ,才會進行傳送。 也就是它只傳送異動過的資料回資料庫進行更新處理。

使用 Update 方法時,原則上必須先設定好 DbDataAdapter 物件的四個指令屬性 (SelectCommand、InsertCommand、UpdateCommand、DeleteCommand),系統才會知道應該如何回存資料。 不過,若針對單一資料表,可以使用 DbCommandBuilder 物件,它為根據 SelectCommand 指令物件內容,快速的自動建立好其他三種指令物件。

protected void btnUpdate_Click(object sender, EventArgs e)
{
    using (SqlConnection connection = CreateTestDBConnection())
    {
        connection.Open();

        SqlCommand cmdSelect = connection.CreateCommand();
        cmdSelect.CommandText = "SELECT Top 10 UserID, UserName FROM tblUser";

        //設定 SelectCommand
        SqlDataAdapter adapter = new SqlDataAdapter(cmdSelect);

        //建立 SqlCommandBuilder
        SqlCommandBuilder cmdBuilder = new SqlCommandBuilder(adapter);

        //將查詢結果填到DataSet
        DataSet dsUsers = new DataSet("Users");
        adapter.Fill(dsUsers, "Users");

        Tools.bindDataToGridView(gv1, dsUsers, "Users");

        //測試更新資料
        dsUsers.Tables[0].Rows[0]["UserName"] = dsUsers.Tables[0].Rows[0]["UserName"].ToString() + "1";
        adapter.Update(dsUsers, "Users");

        //原本應該提供一個 DbCommand給 UpdateCommand 屬性
        //不過這個簡單例子,我們可以使用 SqlCommandBuilder 幫忙即可
        //底下檢視一下,這些自動建立的 DbCommand的樣子

        myDebug.WriteLine(cmdBuilder.GetInsertCommand().CommandText);   //該內容是在第14行指令後才建出來的
        myDebug.WriteLine(cmdBuilder.GetUpdateCommand().CommandText);
        myDebug.WriteLine(cmdBuilder.GetDeleteCommand().CommandText);

        //INSERT INTO [Products] ([ProductName]) VALUES (@p1)
        //UPDATE [Products] SET [ProductName] = @p1 WHERE (([ProductID] = @p2) AND ([ProductName] = @p3))
        //DELETE FROM [Products] WHERE (([ProductID] = @p1) AND ([ProductName] = @p2))
    }
}

批次更新

當執行 Update 方法,系統是將已變更的資料列,一列一列 (row-by-row) 的傳回資料庫以進行更新,若想增加效能,可以設定 UpdateBatchSize 屬性,將傳送筆數增加。 UpdateBatchSize 屬性預設值為1,若設成 0 表示沒有限制。

If the RowState is anything but Unchanged , the Update method sends the change to the database.

RowUpdateEvent 事件

這個事件發生在 Update 方法執行之後,我們可以訂閱這個事件,來檢測批次更新是否有一次傳送多筆資料。

protected void btnBatch_Click(object sender, EventArgs e)
{
    using (SqlConnection connection = CreateTestDBConnection())
    {
        SqlCommand cmdSelect = connection.CreateCommand();
        cmdSelect.CommandType = CommandType.Text;
        cmdSelect.CommandText = "SELECT Top 10 UserID, UserName FROM tblUser";

        SqlDataAdapter da = new SqlDataAdapter(cmdSelect);
        SqlCommandBuilder bldr = new SqlCommandBuilder(da);
        DataSet dsProducts = new DataSet();
                
        connection.Open();

        //==========================================================
        // Fill Data
        //==========================================================
        da.Fill(dsProducts);
        Tools.bindDataToGridView(gv1, dsProducts);

        //==========================================================
        // Update Data  (共3筆)
        //==========================================================
        dsProducts.Tables[0].Rows[0]["UserName"] = System.DateTime.Now.ToString();
        dsProducts.Tables[0].Rows[1]["UserName"] = System.DateTime.Now.ToString();
        dsProducts.Tables[0].Rows[2]["UserName"] = System.DateTime.Now.ToString();

        // handle RowUpdated Event
        da.RowUpdated += SqlRowUpdated;

        da.UpdateBatchSize = 2; 
        da.Update(dsProducts);
    }
}

//這個事件會發生二次
//第一次 RecordsAffected=2
//第一次 RecordsAffected=1

protected void SqlRowUpdated(object sender, SqlRowUpdatedEventArgs e)
{
    DataRow dr = e.Row;
    myDebug.WriteLineBR(e.RecordsAffected.ToString());
}

DbProviderFactory

什麼是 DbProviderFactory 類別

DbProviderFactory 類別是建立 DataProvider 的實作,透過它可以用來動態指定資料庫提供者。

DbProviderFactory 類是 ADO.NET 中的一個 DBProvider 的基底類別,各種特定的 DBProvider 都繼承它,它的架構就像 Design Pattern 中的 Simple Factory 。 我們可以利用這個類別,在程式中不直接指定使用哪一個 DBProvider ,再利用設定檔中設定,然後於執行階段時再決定。

public abstract class DbProviderFactory
{
    protected DbProviderFactory();

    public virtual DbConnection CreateConnection();
    public virtual DbDataAdapter CreateDataAdapter();
    public virtual DbCommandCreateCommand();
    public virtual DbCommandBuilder CreateCommandBuilder();
    public virtual DbParameter CreateParameter();

    public virtual DbConnectionStringBuilder CreateConnectionStringBuilder();
    public virtual DbDataSourceEnumerator CreateDataSourceEnumerator();
    public virtual CodeAccessPermission CreatePermission(PermissionState state);
}

建立 DbProviderFactory 類別

SqlConnection connection = new SqlConnection();
connection.ConnectionString = ConfigurationManager.ConnectionStrings["NorthWindConnectionString"].ConnectionString;
connection.Open();
connection.Close();
string InvariantName = "System.Data.SqlClient";

DbProviderFactory dbFactory = DbProviderFactories.GetFactory(InvariantName);
DbConnection connection = dbFactory.CreateConnection();
connection.ConnectionString = ConfigurationManager.ConnectionStrings["NorthWindConnectionString"].ConnectionString;
connection.Open();
connection.Close();

例外狀況的處理

protected void btnExceptionHandle_Click(object sender, EventArgs e)
{
    using (SqlConnection oConn = CreateTestDBConnection())
    {
        try
        {
            SqlCommand oCmd = oConn.CreateCommand();
            oCmd.CommandText = "RaisError('Custom Error',18,1)";

            oConn.Open();
            oCmd.ExecuteNonQuery();
        }
        catch (DbException ex)
        {
            foreach (DictionaryEntry item in ex.Data)
            {
                myDebug.WriteLine(item.Value.ToString());
            }
        }
    }
}

ADO.NET 常見的 DB 存取錯誤訊息

下列幾項是程式更新資料庫時常會出現的錯誤訊息。

1. 當傳遞擁有已修改資料列的 DataRow 集合時,更新需要有效的 UpdateCommand。

當呼叫 DataAdapter 的 Update 方法時,如果沒有指定適當的 UpdateCommand 物件給 DataAdapter 的話就會發生這個錯誤。 最簡單的解法就是使用 CommandBuilder 來自動產生相對應的 Insert, Delete, Update SQL 語法。

private void GetData()
    {
        String strConn = ConfigurationManager.ConnectionStrings["EmpConnectionString"].ConnectionString;
        SqlConnection objConn = new SqlConnection(strConn);

        SqlCommand objCmd = new SqlCommand();
        objCmd.Connection = objConn;
        objCmd.CommandText = "SELECT ID, Name, Age FROM Emp";

        DataTable dt = new DataTable();
            
        objAdapter.SelectCommand = objCmd;
        objAdapter.Fill(dt);

        dataGridView1.DataSource = dt;
    }

    private void btnUpdate_Click(object sender, EventArgs e)
    {
        //使用 CommandBuilder 來產生相對應的 Insert, Delete, Update SQL語法
        var cmdBuilder = new SqlCommandBuilder();
        cmdBuilder.DataAdapter = objAdapter;  
        objAdapter.Update(((DataTable)dataGridView1.DataSource));
    }

2. 並行違規: UpdateCommand 已經影響必須是 1 記錄的 0。

這個錯誤會發生在同時有兩個人在編輯一筆資料的時候,若第一個人先叫用了 Update ,第二個人若再叫用 Update 就會產生「並行違規: UpdateCommand 已經影響必須是 1 記錄的 0。」的錯誤。 (Concurrency violation: the UpdateCommand affected 0 of the expected 1 records.) 也就是說,程式碼預期資料庫應該會回應 1 筆資料被更新,但是卻回應 0 筆資料被更新,這是不對的,所以產生該項錯誤。

之所以會這個樣子,是因為使用了 SqlCommandBuilder 自動產生的更新命令,不管資料是否包含鍵值,這個更新命令都會更據最後一次 AcceptChanges 時的所有欄位的資料當做 Where 條件去執行變更。例如:

private void btnUpdate2_Click(object sender, EventArgs e)
        {
            int idx = dt.Rows.Count - 1;

             // 若該筆資料一開始為 (ID,Name,Age) = (1, "AAA", 20)

            dt.Rows[idx]["Name"] = "BBB";   
            dt.AcceptChanges();                 // 叫用 AcceptChanges 後,此時資料變更為 (ID,Name,Age) = (1, "BBB", 20)

            dt.Rows[idx]["Name"] = "CCC";

            objAdapter.Update(((DataTable)dataGridView1.DataSource));

            // 當叫用 Update 時,SqlCommandBuilder 自動產生的更新語法為
            // UPDATE EMP SET Name='CCC' WHERE ID=1 AND NAME='BBB' AND Age=20 (更據所有欄位值去當成 where 條件)
            // 這個語法實際對資料庫操作時,會回傳 0 筆被更新,這與預期不符,所以就產生錯誤。
            // 如果資料庫中剛好有一筆符合的資料,那麼就會更新到非預期的資料,但不會產生錯誤。
        }

3. 不會傳回任何重要資料行資訊的 SelectCommand 不支援 UpdateCommand 動態 SQL 的產生。

Error: Dynamic sql generation for the update command is not supported against a select command that does not return any key column information

這個錯誤發生在 SELECT 出來的資料沒有包含主鍵,所以無法動態產生 Update 指令。加入Primary Key 就可以了。

偵測連接事件的資訊

Connection 物件包含一個 InfoMessage 事件,可以用來擷取由資料庫回應出來的一般資訊或錯誤資料。

protected void ConnectionInfoMessage(object sender, SqlInfoMessageEventArgs e)
{
    foreach (SqlError err in e.Errors)
    {
        if (err.Class > 9) // Severity
        {
            Console.WriteLine("Error Message : {0} : State : {1}", err.Message, err.State);
        }
        else
        {
            Console.WriteLine("Info Message : {0} : State : {1}", err.Message, err.State);
        }
    }
}

protected void btnInfoMessage_Click(object sender, EventArgs e)
{
    using (SqlConnection connection = CreateTestDBConnection())
    {
        connection.InfoMessage +=
            new SqlInfoMessageEventHandler(ConnectionInfoMessage);

        connection.Open();

        DbCommandoCmd1 = connection.CreateCommand();
        oCmd1.CommandType = CommandType.StoredProcedure;
        oCmd1.CommandText = "usp_Test";
        oCmd1.ExecuteNonQuery();
    }
}

使用 Transaction 物件

protected void btnTransation_Click(object sender, EventArgs e)
{
    using (SqlConnection connection = CreateTestDBConnection())
    {
        connection.Open();

        SqlTransaction transaction;
        transaction = connection.BeginTransaction();

        try
        {
            SqlCommand cmd = new SqlCommand();

            //必須將 Connection 和 Transaction 二個物件都指定給 Command 物件
            cmd.Connection = connection;
            cmd.Transaction = transaction;

            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandText = "usp_Test";
            cmd.ExecuteNonQuery();
            cmd.CommandText = "usp_Test2";
            cmd.ExecuteNonQuery();

            transaction.Commit();       //commit the transaction
        }
        catch
        {
            transaction.Rollback();     //rollback the transaction
        }
    }
}

非同步資料存取 ( Asynchronous Data Access)

使用非同步資料存取功能時,必須在連線字串中,設定 Asynchronous Processing = true ,並在程式中叫用以下非同的執行方法。

public IAsyncResult BeginExecuteNonQuery();
public IAsyncResult BeginExecuteNonQuery(AsyncCallback callback, object stateObject);

public IAsyncResult BeginExecuteReader();
public IAsyncResult BeginExecuteReader(AsyncCallback callback, object stateObject);

public IAsyncResult BeginExecuteXmlReader();
public IAsyncResult BeginExecuteXmlReader(AsyncCallback callback, object stateObject);
protected void btnAsynchronous_Click(object sender, EventArgs e)
{
    // 必須在連線設定中指定 Asynchronous Processing = true 

    string connString = @"
    Data Source = VITO-2011W7\W7_SQLSVR_2008; Initial Catalog = Vito2012; User ID=vito; Password=vito1234;
    Asynchronous Processing = true;
    MultipleActiveResultSets = true";

    SqlConnection conn1 = new SqlConnection(connString);
    SqlConnection conn2 = new SqlConnection(connString);
    SqlConnection conn3 = new SqlConnection(connString);

    SqlCommand cmd1 = conn1.CreateCommand();
    cmd1.CommandType = CommandType.StoredProcedure;
    cmd1.CommandText = "usp_AsysTest";

    SqlCommand cmd2 = conn2.CreateCommand();
    cmd2.CommandType = CommandType.StoredProcedure;
    cmd2.CommandText = "usp_AsysTest";

    SqlCommand cmd3 = conn3.CreateCommand();
    cmd3.CommandType = CommandType.StoredProcedure;
    cmd3.CommandText = "usp_AsysTest";

    DateTime dt1 = DateTime.Now;

    conn1.Open();
    conn2.Open();
    conn3.Open();

    IAsyncResult result1 = cmd1.BeginExecuteNonQuery();
    IAsyncResult result2 = cmd2.BeginExecuteNonQuery();
    IAsyncResult result3 = cmd3.BeginExecuteNonQuery();

    //result1.AsyncWaitHandle.WaitOne();
    //result2.AsyncWaitHandle.WaitOne();
    //result3.AsyncWaitHandle.WaitOne();

    cmd1.EndExecuteNonQuery(result1);
    cmd2.EndExecuteNonQuery(result2);
    cmd3.EndExecuteNonQuery(result3);

    DateTime dt2 = DateTime.Now;
    TimeSpan timespan1 = dt2 - dt1;
    myDebug.WriteLineBR(timespan1.ToString());  // 00:00:05.0012860

    conn1.Close();
    conn2.Close();
    conn3.Close();
}

如何使用大型二進位物件 (Binary Large OBject, BLOB)

什麼是大型二進位物件

在 SQL 中,有以下幾種比較大型的欄位型態:

型別Unicode長度最大長度
image 可變2^31-1
binary 固定8,000
varbinary 可變2^31-1
text 可變2^31-1
ntext Unicode可變2^30-1
varchar 固定8,000
nvarchar Unicode可變2^31-1 (2GB)

因為 MS 將在未來的版本中,移除 ntext、text 和 image 等資料類型。 所以請避免在新的開發工作中使用這些資料類型。 可改用 nvarchar(max)、varchar(max) 和 varbinary(max) 取代。

更多說明,可參考MSDN:

寫入 BLOG 資料

protected void btnWriteBLOB_Click(object sender, EventArgs e)
{
    string sFilePath = @"D:\VitoBlogPic\test1.jpg";
    byte[] photo = GetPhoto(sFilePath);

    using (SqlConnection oConn = CreateTestDBConnection())
    {
        SqlCommand oCmd = oConn.CreateCommand();
        oCmd.CommandType = CommandType.Text;
        oCmd.CommandText = "INSERT Employee(Name,Picture) Values (@Name, @Picture)";

        oCmd.Parameters.Add("@Name", SqlDbType.NVarChar, 150).Value = "vito";
        oCmd.Parameters.Add("@Picture", SqlDbType.VarBinary, photo.Length).Value = photo;

        try
        {
            oConn.Open();
            oCmd.ExecuteNonQuery();
        }
        catch (Exception ex)
        {
            myDebug.WriteLine(ex.Message);
        }
    }
}
public static byte[] GetPhoto(string filePath)
{
    FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
    BinaryReader reader = new BinaryReader(stream);
    byte[] photo = reader.ReadBytes((int)stream.Length);
    reader.Close();
    stream.Close();
    return photo;
}

讀取 BLOG 資料

protected void btnReadBLOB_Click(object sender, EventArgs e)
{
    using (SqlConnection oConn = CreateTestDBConnection())
    {
        SqlCommand oCmd = oConn.CreateCommand();
        oCmd.CommandType = CommandType.Text;
        oCmd.CommandText = "Select * from Employee where EmployeeID='9411993'";

        try
        {
            oConn.Open();
            SqlDataReader reader = oCmd.ExecuteReader();

            if (reader.Read())
            {
                byte[] photo = reader["Picture"] as byte[];

                string sFilePath = @"C:\EmployeePic.jpg";
                FileStream fs = new FileStream(sFilePath, FileMode.OpenOrCreate, FileAccess.Write);
                fs.Write(photo, 0, photo.Length);
                fs.Close();
            }
        }
        catch (Exception ex)
        {
            myDebug.WriteLine(ex.Message);
        }
    }
}

上面例子會將資料庫中的資料讀取後儲存成檔案,若我們處理的 BLOB 是圖檔資料,我們可以建立一個 「泛型處理常式」來處理網頁讀取圖檔的需求,以方便將圖型資料顯示在網頁上。

1) 假設我們在網頁上放置了一個 Image 控制項

<asp:Image ID="Image1" runat="server" />



2) 我們可以在 Code Behind 中,指定要顯示的資料

Image1.ImageUrl = @"../Common/Image.ashx?EmpID=9411993";

3) 然後撰寫一個泛型處理常式

public class Image : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        string EmpID = context.Request.QueryString["EmpID"];
        string ConStr = WebConfigurationManager.ConnectionStrings["TestDBConnectionString"].ConnectionString;
        using (SqlConnection oConn = new SqlConnection(ConStr))
        {
            SqlCommand oCmd = oConn.CreateCommand();
            oCmd.CommandType = CommandType.Text;
            oCmd.CommandText = "Select Picture from Employee where EmployeeID=@EmpID";
            oCmd.Parameters.Add(new SqlParameter("@EmpID", EmpID));
            try
            {
                oConn.Open();
                SqlDataReader reader = oCmd.ExecuteReader();

                if (reader.Read())
                {
                    if (!Convert.IsDBNull(reader["Picture"]))
                    {
                        byte[] photo = reader["Picture"] as byte[];
                        context.Response.ContentType = "image/jpeg";
                        context.Response.BinaryWrite(photo);
                    }
                    else
                        context.Response.Write("./images/noimage.gif");
                }
            }
            catch (Exception ex)
            {
                myDebug.WriteLine(ex.Message);
            }
        }

    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

沒有留言:

張貼留言