浅析ado.net获取数据库元数据信息

写这个文章源于早先对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 public static void DeriveParameters(SqlCommandcommand)
2 {
3SqlConnection.SqlClientPermission.Demand();
4if(command==null)
5{
6//throwanexception
7}

8command.DeriveParameters();
9}

来看一下SqlCommand的DeriveParameters方法:
1 internal void DeriveParameters()
2 {
3
4//Validatecommandtype(isstoredprocedure?)andcommandinfo
5
6
7//Retrievecommandtextdetail
8string[]txtCommand=ADP.ParseProcedureName(this.CommandText);
9
10SqlCommandcmdDeriveCommand=null;
11
12this.cmdText="sp_procedure_params_rowset";
13if(txtCommand[1]!=null)
14{
15this.cmdText="["+txtCommand[1]+"].."+this.cmdText;
16
17if(txtCommand[0]!=null)
18{
19this.cmdText=txtCommand[0]+"."+this.cmdText;
20}

21
22cmdDeriveCommand=newSqlCommand(this.cmdText,this.Connection);
23}

24else
25{
26cmdDeriveCommand=newSqlCommand(this.cmdText,this.Connection);
27}

28cmdDeriveCommand.CommandType=CommandType.StoredProcedure;
29cmdDeriveCommand.Parameters.Add(newSqlParameter("@procedure_name",SqlDbType.NVarChar,0xff));
30cmdDeriveCommand.Parameters[0].Value=txtCommand[3];
31ArrayListparms=newArrayList();
32try
33{
34try
35{
36using(SqlDataReaderdrParam=cmdDeriveCommand.ExecuteReader())
37{
38SqlParameterparameter=null;
39while(drParam.Read())
40{
41parameter=newSqlParameter();
42parameter.ParameterName=(string)drParam["PARAMETER_NAME"];
43parameter.SqlDbType=MetaType.GetSqlDbTypeFromOleDbType((short)drParam["DATA_TYPE"],(string)drParam["TYPE_NAME"]);
44objectlen=drParam["CHARACTER_MAXIMUM_LENGTH"];
45if(lenisint)
46{
47parameter.Size=(int)len;
48}

49parameter.Direction=this.ParameterDirectionFromOleDbDirection((short)drParam["PARAMETER_TYPE"]);
50if(parameter.SqlDbType==SqlDbType.Decimal)
51{
52parameter.Scale=(byte)(((short)drParam["NUMERIC_SCALE"])&0xff);
53parameter.Precision=(byte)(((short)drParam["NUMERIC_PRECISION"])&0xff);
54}

55parms.Add(parameter);
56}

57}

58}

59finally
60{
61cmdDeriveCommand.Connection=null;
62}

63}

64catch
65{
66throw;
67}

68
69if(params.Count==0)
70{
71//throwanexceptionthatcurrentstoredproceduredoesnotexist
72}

73
74this.Parameters.Clear();
75foreach(objectparminparms)
76{
77this._parameters.Add(parm);
78}

79}

ADP.ParseProcedureName其实就是获取存储过程命令的细节信息,有兴趣的可以反编译来看看。

纵观整个方法,有效性验证-〉获取命令字符串-〉执行查询-〉填充参数列表-〉返回。应该是非常简洁明朗的,最多也就是在数据库Query的阶段需要有一个来回,其他操作根本就谈不上有什么复杂度,而且也不存在大数据的对象,对性能的损耗谈不上多巨大。

下面来看看FillSchema的处理过程

FillSchema方法

这个部分因为代码比较多,所以我就抽关键的部分来看一下。

首先,FillSchema是DataAdapter类定义的一个方法,而具体实现则是在该类的子类DBDataAdapter中完成的(SqlDataAdapter继承于DBDataAdapter)。

通过反编译,可以发现FillSchema的关键处理步骤是在其调用私有方法FillSchemaFromCommand来完成的。简单看一下该方法体的内容:

1 private DataTable[]FillSchemaFromCommand( object data,SchemaTypeschemaType,IDbCommandcommand, string srcTable,CommandBehaviorbehavior)
2 {
3IDbConnectionconnection=DbDataAdapter.GetConnection(command,"FillSchema");
4ConnectionStatestate=ConnectionState.Open;
5DataTable[]arrTables=newDataTable[0];
6try
7{
8try
9{
10DbDataAdapter.QuietOpen(connection,outstate);
11using(IDataReaderreader=command.ExecuteReader((behavior|CommandBehavior.SchemaOnly)|CommandBehavior.KeyInfo))
12{
13if(reader==null)
14{
15returnarrTables;
16}

17inttblIndex=0;
18while(true)
19{
20if(0<reader.FieldCount)
21{
22try
23{
24stringtxtTableName=null;
25SchemaMappingmapping=newSchemaMapping(this,reader,true);
26if(dataisDataTable)
27{
28mapping.DataTable=(DataTable)data;
29}

30else
31{
32mapping.DataSet=(DataSet)data;
33txtTableName=DbDataAdapter.GetSourceTableName(srcTable,tblIndex);
34}

35mapping.SetupSchema(schemaType,txtTableName,false,null,null);
36DataTablecurrentTable=mapping.DataTable;
37if(currentTable!=null)
38{
39arrTables=DbDataAdapter.AddDataTableToArray(arrTables,currentTable);
40}

41}

42finally
43{
44tblIndex++;
45}

46}

47if(!reader.NextResult())
48{
49returnarrTables;
50}

51}

52}

53}

54finally
55{
56DbDataAdapter.QuietClose(connection,state);
57}

58}

59catch
60{
61throw;
62}

63returnarrTables;
64}


首先,该操作含有一个数据库的Query操作,这里其实是调用DBDataAdapter的SelectCommand的对象,执行一次查询,然后遍历查询返回的所有表,每遍历到一个表的时候,通过该表的信息实例化一个SchemaMapping对象,再有该对象创建为DataSet/DataTable创建架构信息。

这里,DataSet/DataTable是作为参数提供的,整个处理过程,首先必然的需要完成一次查询操作,由于使用IDataReader,所以在查询之后的所有操作期间,连接是保持着的,这一定程度上占用了一些资源(也可以说这些资源还不算太昂贵);其次,实例化一个SchemaMapping对象(该对象是内部类,我在MSDN上没有查到相关介绍性资料),我简单看了一下这个类的代码,在我看来,它的处理过程应该是占据了整个过程蛮大一部分资源的,这方面属于个人见解。

由于我的认识上的有限,也为了保证文章的内容无误导,暂且说到这里。这个方法的进一步讨论希望留给有兴趣的朋友。

总结

以上是我对这两个方法认识方面简单的一个概括,其实从上面的描述,也打消了我原先认为的这两个方法在获取元数据上有本质的差别。个人认为,之所以获取结构性元数据的消耗大,是因为获取逻辑的繁琐以及使用的对象的庞大,而参数信息相对而言完全属于轻量级的东西,所以所谓性能上的差异并非因为获取机制的本质差异引起的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值