写这个文章源于早先对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就是封装了该方法的调用:
1
publicstaticvoidDeriveParameters(SqlCommandcommand)
2

{
3
SqlConnection.SqlClientPermission.Demand();
4
if(command==null)
5

{
6
//throwanexception
7
}
8
command.DeriveParameters();
9
}
来看一下SqlCommand的DeriveParameters方法:
1
internalvoidDeriveParameters()
2

{
3

4
//Validatecommandtype(isstoredprocedure?)andcommandinfo
5

6
7
//Retrievecommandtextdetail
8
string[]txtCommand=ADP.ParseProcedureName(this.CommandText);
9
10
SqlCommandcmdDeriveCommand=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=newSqlCommand(this.cmdText,this.Connection);
23
}
24
else
25

{
26
cmdDeriveCommand=newSqlCommand(this.cmdText,this.Connection);
27
}
28
cmdDeriveCommand.CommandType=CommandType.StoredProcedure;
29
cmdDeriveCommand.Parameters.Add(newSqlParameter("@procedure_name",SqlDbType.NVarChar,0xff));
30
cmdDeriveCommand.Parameters[0].Value=txtCommand[3];
31
ArrayListparms=newArrayList();
32
try
33

{
34
try
35

{
36
using(SqlDataReaderdrParam=cmdDeriveCommand.ExecuteReader())
37

{
38
SqlParameterparameter=null;
39
while(drParam.Read())
40

{
41
parameter=newSqlParameter();
42
parameter.ParameterName=(string)drParam["PARAMETER_NAME"];
43
parameter.SqlDbType=MetaType.GetSqlDbTypeFromOleDbType((short)drParam["DATA_TYPE"],(string)drParam["TYPE_NAME"]);
44
objectlen=drParam["CHARACTER_MAXIMUM_LENGTH"];
45
if(lenisint)
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
//throwanexceptionthatcurrentstoredproceduredoesnotexist
72
}
73
74
this.Parameters.Clear();
75
foreach(objectparminparms)
76

{
77
this._parameters.Add(parm);
78
}
79
}
ADP.ParseProcedureName其实就是获取存储过程命令的细节信息,有兴趣的可以反编译来看看。
纵观整个方法,有效性验证-〉获取命令字符串-〉执行查询-〉填充参数列表-〉返回。应该是非常简洁明朗的,最多也就是在数据库Query的阶段需要有一个来回,其他操作根本就谈不上有什么复杂度,而且也不存在大数据的对象,对性能的损耗谈不上多巨大。
下面来看看FillSchema的处理过程
FillSchema方法
这个部分因为代码比较多,所以我就抽关键的部分来看一下。
首先,FillSchema是DataAdapter类定义的一个方法,而具体实现则是在该类的子类DBDataAdapter中完成的(SqlDataAdapter继承于DBDataAdapter)。
通过反编译,可以发现FillSchema的关键处理步骤是在其调用私有方法FillSchemaFromCommand来完成的。简单看一下该方法体的内容:
1
privateDataTable[]FillSchemaFromCommand(objectdata,SchemaTypeschemaType,IDbCommandcommand,stringsrcTable,CommandBehaviorbehavior)
2

{
3
IDbConnectionconnection=DbDataAdapter.GetConnection(command,"FillSchema");
4
ConnectionStatestate=ConnectionState.Open;
5
DataTable[]arrTables=newDataTable[0];
6
try
7

{
8
try
9

{
10
DbDataAdapter.QuietOpen(connection,outstate);
11
using(IDataReaderreader=command.ExecuteReader((behavior|CommandBehavior.SchemaOnly)|CommandBehavior.KeyInfo))
12

{
13
if(reader==null)
14

{
15
returnarrTables;
16
}
17
inttblIndex=0;
18
while(true)
19

{
20
if(0<reader.FieldCount)
21

{
22
try
23

{
24
stringtxtTableName=null;
25
SchemaMappingmapping=newSchemaMapping(this,reader,true);
26
if(dataisDataTable)
27

{
28
mapping.DataTable=(DataTable)data;
29
}
30
else
31

{
32
mapping.DataSet=(DataSet)data;
33
txtTableName=DbDataAdapter.GetSourceTableName(srcTable,tblIndex);
34
}
35
mapping.SetupSchema(schemaType,txtTableName,false,null,null);
36
DataTablecurrentTable=mapping.DataTable;
37
if(currentTable!=null)
38

{
39
arrTables=DbDataAdapter.AddDataTableToArray(arrTables,currentTable);
40
}
41
}
42
finally
43

{
44
tblIndex++;
45
}
46
}
47
if(!reader.NextResult())
48

{
49
returnarrTables;
50
}
51
}
52
}
53
}
54
finally
55

{
56
DbDataAdapter.QuietClose(connection,state);
57
}
58
}
59
catch
60