寫這個文章源於早先對ADO.Net獲取數據庫元數據上的認識,去年我在閱讀ADO.Net Core Reference的時候曾經注意過DataSet的FillSchema的這個方法。這方面,在我之前的隨筆中提到過Typed DataSet,而FillSchem與WriteXmlSchema的結合使用可以獲得數據庫的表結構架構,從而使用相應工具生成強類型的DataSet。但是我記得作者建議在具體應用開發中盡量少用FillSchema這個方法,因為出於性能考慮,其一般只適合作為測試過程中的一個方法。
當時我的理解就是,這是一個獲取數據庫元數據的一個方便的方法,但是由於其對性能的影響,因此通常應用中比較少用。而在我後面的開發中也未曾有機會接觸這個方法。
今年早先1月份的時候看DAAB,注意到其封裝的DataCommand對象提供了動態獲取存儲過程信息的支持:DeriveParameters。當時我的第一印象是,這也是獲取數據庫的“元數據”,因為之前有過FillSchema對性能影響上的認識,我當時就產生了一個問號:這樣做適合嗎?自動填充Command對象的Parameter集合,會影響應用程序的性能嗎?
就此我也請教過M$的專家,給我的回答是兩者機制不同,後者對性能影響不大。
昨日翻倒年初對這個問題疑惑而提的一篇帖子,突然很想進一步找找這兩中方法的區別之處,簡單了解了一下,以下做個簡單的歸納。
DeriveParameters方法
先說簡單的一個。DeriveParameters是SqlCommandBuilder類的一個公共方法,提供一個SqlCommannd的參數,該Command對象作為獲取到的Parameters的存放容器。其實SqlCommand本身就有一個DeriveParameters的方法,但是它是內部方法,而SqlCommandBuilder.DeriveParameters就是封裝了該方法的調用:
1public static void DeriveParameters(SqlCommand command)
2{
3 SqlConnection.SqlClientPermission.Demand();
4 if (command == null)
5 {
6 // throw an exception
7 }
8 command.DeriveParameters();
9}
來看一下SqlCommand的DeriveParameters方法:
1internal void DeriveParameters()
2{
3
4 // Validate command type(is storedprocedure?) and command info
5
6
7 // Retrieve command text detail
8 string[] txtCommand = ADP.ParseProcedureName(this.CommandText);
9
10 SqlCommand cmdDeriveCommand = null;
11
12 this.cmdText = "sp_procedure_params_rowset";
13 if (txtCommand[1] != null)
14 {
15 this.cmdText = "[" + txtCommand[1] + "].." + this.cmdText;
16
17 if (txtCommand[0] != null)
18 {
19 this.cmdText = txtCommand[0] + "." + this.cmdText;
20 }
21
22 cmdDeriveCommand = new SqlCommand(this.cmdText, this.Connection);
23 }
24 else
25 {
26 cmdDeriveCommand = new SqlCommand(this.cmdText, this.Connection);
27 }
28 cmdDeriveCommand.CommandType = CommandType.StoredProcedure;
29 cmdDeriveCommand.Parameters.Add(new SqlParameter("@procedure_name", SqlDbType.NVarChar, 0xff));
30 cmdDeriveCommand.Parameters[0].Value = txtCommand[3];
31 ArrayList parms = new ArrayList();
32 try
33 {
34 try
35 {
36 using (SqlDataReader drParam = cmdDeriveCommand.ExecuteReader())
37 {
38 SqlParameter parameter = null;
39 while (drParam.Read())
40 {
41 parameter = new SqlParameter();
42 parameter.ParameterName = (string) drParam["PARAMETER_NAME"];
43 parameter.SqlDbType = MetaType.GetSqlDbTypeFromOleDbType((short) drParam["DATA_TYPE"], (string) drParam["TYPE_NAME"]);
44 object len = drParam["CHARACTER_MAXIMUM_LENGTH"];
45 if (len is int)
46 {
47 parameter.Size = (int) len;
48 }
49 parameter.Direction = this.ParameterDirectionFromOleDbDirection((short) drParam["PARAMETER_TYPE"]);
50 if (parameter.SqlDbType == SqlDbType.Decimal)
51 {
52 parameter.Scale = (byte) (((short) drParam["NUMERIC_SCALE"]) & 0xff);
53 parameter.Precision = (byte) (((short) drParam["NUMERIC_PRECISION"]) & 0xff);
54 }
55 parms.Add(parameter);
56 }
57 }
58 }
59 finally
60 {
61 cmdDeriveCommand.Connection = null;
62 }
63 }
64 catch
65 {
66 throw;
67 }
68
69 if (params.Count == 0)
70 {
71 // throw an exception that current storedprocedure does not exist
72 }
73
74 this.Parameters.Clear();
75 foreach (object parm in parms)
76 {
77 this._parameters.Add(parm);
78 }
79}
ADP.ParseProcedureName其實就是獲取存儲過程命令的細節信息,有興趣的可以反編譯來看看。
縱觀整個方法,有效性驗證-〉獲取命令字符串-〉執行查詢-〉填充參數列表-〉返回。應該是非常簡潔明朗的,最多也就是在數據庫Query的階段需要有一個來回,其他操作根本就談不上有什麼復雜度,而且也不存在大數據的對象,對性能的損耗談不上多巨大。
下面來看看FillSchema的處理過程
FillSchema方法
這個部分因為代碼比較多,所以我就抽關鍵的部分來看一下。
首先,FillSchema是DataAdapter類定義的一個方法,而具體實現則是在該類的子類DBDataAdapter中完成的(SqlDataAdapter繼承於DBDataAdapter)。
通過反編譯,可以發現FillSchema的關鍵處理步驟是在其調用私有方法FillSchemaFromCommand來完成的。簡單看一下該方法體的內容: