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


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


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


{3


4
//Validatecommandtype(isstoredprocedure?)andcommandinfo5


6

7
//Retrievecommandtextdetail8
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
else25


{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
try33


{34
try35


{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
finally60


{61
cmdDeriveCommand.Connection=null;62
}63
}64
catch65


{66
throw;67
}68

69
if(params.Count==0)70


{71
//throwanexceptionthatcurrentstoredproceduredoesnotexist72
}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来完成的。简单看一下该方法体的内容:
privateDataTable[]FillSchemaFromCommand(objectdata,SchemaTypeschemaType,IDbCommandcommand,stringsrcTable,CommandBehaviorbehavior)2


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


{8
try9


{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
try23


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


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


{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
finally43


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


{49
returnarrTables;50
}51
}52
}53
}54
finally55


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


{61
throw;62
}63
returnarrTables;64
}
首先,该操作含有一个数据库的Query操作,这里其实是调用DBDataAdapter的SelectCommand的对象,执行一次查询,然后遍历查询返回的所有表,每遍历到一个表的时候,通过该表的信息实例化一个SchemaMapping对象,再有该对象创建为DataSet/DataTable创建架构信息。
这里,DataSet/DataTable是作为参数提供的,整个处理过程,首先必然的需要完成一次查询操作,由于使用IDataReader,所以在查询之后的所有操作期间,连接是保持着的,这一定程度上占用了一些资源(也可以说这些资源还不算太昂贵);其次,实例化一个SchemaMapping对象(该对象是内部类,我在MSDN上没有查到相关介绍性资料),我简单看了一下这个类的代码,在我看来,它的处理过程应该是占据了整个过程蛮大一部分资源的,这方面属于个人见解。
由于我的认识上的有限,也为了保证文章的内容无误导,暂且说到这里。这个方法的进一步讨论希望留给有兴趣的朋友。
总结
以上是我对这两个方法认识方面简单的一个概括,其实从上面的描述,也打消了我原先认为的这两个方法在获取元数据上有本质的差别。个人认为,之所以获取结构性元数据的消耗大,是因为获取逻辑的繁琐以及使用的对象的庞大,而参数信息相对而言完全属于轻量级的东西,所以所谓性能上的差异并非因为获取机制的本质差异引起的。
3203

被折叠的 条评论
为什么被折叠?



