1. 程式人生 > >.net中呼叫儲存過程

.net中呼叫儲存過程

摘要:在一個專案的開發中,經常會呼叫資料庫中的儲存過程。可是,幾乎所有儲存過程的呼叫都是同一個模式,主要區別就在於建立的每個引數型別、值等不一樣。那麼,能不能實現通過一個函式(或者類)呼叫所有的儲存過程呢?本文在利用資料庫提供的系統表原理上,實現了統一呼叫的方法,該方法只需要提供要呼叫的儲存過程名,以及呼叫時提供具體的引數值就可實現任何儲存過程的呼叫。

Abstract: We have to call stored procedures of database systems during a development of a project. However, calling a stored procedures are almost the same, the main difference is the difference between parameters’ type or value etc. Can we call any stored procedures through a function (or a class)? Based on the system tables provided by database systems, We wrote a class to call any stored procedures in this article. To call a stored procedure, the only parameters you provide are the name of the stored procedure and the value of all parameters of the stored procedure.

1.       引言

在各種系統開發中,使用儲存過程是一個良好的習慣,不僅可以帶來臨時表、函式、遊標等特性,而且除錯、升級、維護都變得方便。在儲存過程中能夠把資料經過處理再返回,這樣能夠對資料提供更多的分析和控制。

在儲存過程的呼叫中,我們發現儲存過程的呼叫都幾乎是如下的模式:

1.宣告SqlConnection

2.宣告SqlCommand,並且設定其Connection屬性為剛宣告的SqlConnection例項,設定CommandName為儲存過程名,CommandType為儲存過程。

3.往剛宣告的SqlCommand例項的Parameters集合中新增所有的儲存過程呼叫需要的引數

4.呼叫SqlCommand的ExecuteReader()方法來得到儲存過程的返回行集

4.宣告SqlDataAdapter和DataSet,設定SqlDataAdapter的SelectCommand屬性為3中宣告的例項,再呼叫其Fill方法來把返回的行集填充到DataSet中

5.關閉SqlConnection物件

6.釋放宣告的各物件例項

(說明:4指的是兩種資料提取方法)

在這個呼叫過程中,我們發現幾乎所有的儲存過程呼叫都是這個模式,之間的區別就在第2步中的儲存過程名不同和第3步中各個儲存過程呼叫使用的引數是不一樣的,他們有引數名字、方向、資料型別、長度等的區別。

那麼,有沒有一種方法可以實現所有的儲存過程呼叫?即只需要提供儲存過程名,然後把引數值傳入呼叫方法即可實現儲存過程的呼叫,再用某些資料結構來儲存返回的行集、傳出引數值、過程返回值。經過研究SQL Server的系統表,我們發現這個想法是切實可行的。

2.系統表與資訊結構檢視

       SQL Server等關係型資料庫都將元資料以某種方式儲存在資料庫中,在SQL Server中就是系統資料庫和系統表。安裝SQL Server後會自動生成四個系統資料庫:master, model, msdb與tempdb。master資料庫是SQL Server中所有系統級資訊的倉庫。登入帳號、配置設定、系統儲存過程和其他資料庫的存在性都記錄在master資料庫中。msdb資料庫儲存SQL Server Agent的資訊。定義作業、操作員和警報時,他們存放在msdb中。model是個模框,用於所有使用者生成的資料庫。生成新資料庫時,將model複製,建立所要的物件。tempdb儲存SQL Server中的臨時物件。顯示生成的臨時表和臨時儲存過程以及系統生成的臨時物件都利用tempdb。[1]

       而且每個資料庫中都有自己的系統表。這些系統表被用來儲存配置和物件資訊。從這些系統表中,我們就可以得到每個儲存過程的所有引數的資訊。syscolumns表中就儲存了這些資訊。其中有引數名、型別、長度、方向等需要用到我們方法中的資訊。

       不過,系統表中的欄位會隨著SQL Server版本的變化而變化。比如syscolumns中的type和xtype就是這樣的一個變化例子,他們都儲存了型別的資訊。要讓我們的方法適應SQL Server的版本變化要求,就要用到資訊結構檢視。

       ANSI-92將資訊結構檢視定義為一組提供系統資料的檢視。通過利用該檢視,可以將實際系統表從應用程式中隱藏起來。系統表的改變就不會影響到應用程式,這樣應用程式就可以獨立於資料庫廠家和版本。[1]

       ANSI-92和SQL Server支援用三段命名結構引用本地伺服器上的物件。ANSI-92術語稱為catalog.schema.object,而SQL Server稱為database.owner.object。[1]比如我們要找到所有儲存過程的所有引數資訊,就可以用:

       select * from INFORMATION_SCHEMA.PARAMETERS

如果要找到某個儲存過程的所有引數資訊,就是:

       select * from INFORMATION_SCHEMA.PARAMETERS where SPECIFIC_NAME =’Proc1’

有了資訊結構檢視,我們的問題就解決了一大半了。下面我們看如何在.NET中實現我們的方法。

3.實現方法

實現的重點就放在如何根據儲存過程名來得到它的所有的引數資訊,再根據這些引數資訊自動的建立各個引數。為了讓這些動作自動化,宣告SqlConnection、SqlCommand、SqlParameter的過程,建立各個SqlParameter的過程對使用者來說都應該不可見。使用者唯一需要提供的就是儲存過程的名字,然後就是在呼叫的時候提供各個引數,甚至連他們的型別都不需要提供。

3.1獲得和建立儲存過程的引數

如何獲得並且建立要呼叫的儲存過程的引數是一個重點,通過資訊結構檢視我們可以自動的實現這個步驟。

// 獲得和建立儲存過程的引數
private void GetProcedureParameter(params object[] parameters)
{
        SqlCommand myCommand2 = new SqlCommand();
        myCommand2.Connection = this.myConnection;
        myCommand2.CommandText = "select * from INFORMATION_SCHEMA.PARAMETERS where SPECIFIC_NAME='" +this.ProcedureName+ "' order by ORDINAL_POSITION";
        SqlDataReader reader = null;
        reader = myCommand2.ExecuteReader();
                // 建立返回引數
                myParameter = new SqlParameter();
                myParameter.ParameterName = "@Value";
                myParameter.SqlDbType = SqlDbType.Int;
                myParameter.Direction = ParameterDirection.ReturnValue;
                myCommand.Parameters.Add(myParameter);
                int i = 0;
                // 建立各個引數,在這個地方可以自動的建立SqlParameter的型別,值,方向等屬性
                while(reader.Read())
                {
                    myParameter = new SqlParameter();
                    myParameter.ParameterName = reader["PARAMETER_NAME"].ToString();
                    myParameter.Direction = reader["PARAMETER_MODE"].ToString()=="IN"?ParameterDirection.Input:ParameterDirection.Output;
                    switch(reader["DATA_TYPE"].ToString())
                    {
                            case "int" :
                                if(myParameter.Direction == ParameterDirection.Input)
                                        myParameter.Value = (int)parameters[i];
                                myParameter.SqlDbType = SqlDbType.Int;
                                break;
                           //...省略了很多具體的型別處理
                                default : break;
                    }
                    i++;
                    myCommand.Parameters.Add(myParameter);
                }
}

3.2返回結果資料集、返回值、傳出引數集

建立好儲存過程的引數之後,我們就可以呼叫這個儲存過程了。由於在.NET中,常用的返回結果集的類為SqlDataReader和DataSet,而SqlDataReader必須在保持連線的狀態下才可以使用,DataSet卻不需要。在我們的實現中,連線應該在呼叫之後就斷開,因此採用DataSet來儲存返回結果集。

public SqlResult Call(params object[] parameters)
{
        // SqlResult是自己定義的用於儲存結果資料集、返回值、傳出引數集的類
        SqlResult result = new SqlResult();
        // 根據需要定義自己的連線字串
        myConnection  = new SqlConnection(ConnectionString);
        myCommand = new SqlCommand(this.ProcedureName, myConnection);
        myCommand.CommandType = CommandType.StoredProcedure;
        SqlDataAdapter myAdapter = new SqlDataAdapter(myCommand);
        myConnection.Open();
        // 獲得和建立儲存過程的引數,並且設定好值
        GetProcedureParameter(parameters);
        myAdapter.Fill(result.dataSet, "Table");
        // 獲得儲存過程的傳出引數值和名字對,儲存在一個Hashtable中
        GetOutputValue(result);
        // 在這裡釋放各種資源,斷開連線
        myAdapter.Dispose();
        myCommand.Dispose();
        myConnection.Close();
        myConnection.Dispose();
        return result;
}

4.進一步工作

       雖然我們在這裡的實現是針對SQL Server資料庫,但是對於任何提供了資訊結構檢視,符合ANSI-92標準,或者是提供了元資料的資料庫都可以使用這種方法來實現。我們把它封裝成一個SqlProcedure類,在需要的時候可以很簡單的就呼叫了儲存過程,減少了大量基本上是重複的程式碼工作。

       為了讓SqlProcedure類支援更過的資料型別,在GetProcedureParameter()方法中需要根據自己的需要來分析各個引數的型別、方向、長度、預設值等資訊,然後來建立這個引數。基本上任何型別都是能夠實現的,甚至連image型別都可以採用這種方式建立。這樣這個類就可以很通用,在任何專案中都可以發揮作用。