`

浅析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就是封装了该方法的调用:

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来完成的。简单看一下该方法体的内容:

 1private DataTable[] FillSchemaFromCommand(object data, SchemaType schemaType, IDbCommand command, string srcTable, CommandBehavior behavior)
 2{
 3      IDbConnection connection = DbDataAdapter.GetConnection(command, "FillSchema");
 4      ConnectionState state = ConnectionState.Open;
 5      DataTable[] arrTables = new DataTable[0];
 6      try
 7      {
 8            try
 9            {
10                  DbDataAdapter.QuietOpen(connection, out state);
11                  using (IDataReader reader = command.ExecuteReader((behavior | CommandBehavior.SchemaOnly) | CommandBehavior.KeyInfo))
12                  {
13                        if (reader == null)
14
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics