浅析ado.net获取数据库元数据信息 DeriveParameters
写这个文章源于早先对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就是封装了该方法的调用:
 public static void DeriveParameters(SqlCommand command)
public static void DeriveParameters(SqlCommand command)2
 {
{3
 SqlConnection.SqlClientPermission.Demand();
      SqlConnection.SqlClientPermission.Demand();4
 if (command == null)
      if (command == null)5
 {
      {6
 // throw an exception
            // throw an exception7
 }
      }8
 command.DeriveParameters();
      command.DeriveParameters();9
 }
}
来看一下SqlCommand的DeriveParameters方法:
 internal void DeriveParameters()
internal void DeriveParameters()2
 {
{3
 
      
4
 // Validate command type(is storedprocedure?) and command info
      // Validate command type(is storedprocedure?) and command info5
 
      
6

7
 // Retrieve command text detail
      // Retrieve command text detail8
 string[] txtCommand = ADP.ParseProcedureName(this.CommandText);
      string[] txtCommand = ADP.ParseProcedureName(this.CommandText);9

10
 SqlCommand cmdDeriveCommand = null;
      SqlCommand cmdDeriveCommand = null;11

12
 this.cmdText = "sp_procedure_params_rowset";
      this.cmdText = "sp_procedure_params_rowset";13
 if (txtCommand[1] != null)
      if (txtCommand[1] != null)14
 {
      {15
 this.cmdText = "[" + txtCommand[1] + "].." + this.cmdText;
            this.cmdText = "[" + txtCommand[1] + "].." + this.cmdText;16

17
 if (txtCommand[0] != null)
            if (txtCommand[0] != null)18
 {
            {19
 this.cmdText = txtCommand[0] + "." + this.cmdText;
                  this.cmdText = txtCommand[0] + "." + this.cmdText;20
 }
            }21

22
 cmdDeriveCommand = new SqlCommand(this.cmdText, this.Connection);
            cmdDeriveCommand = new SqlCommand(this.cmdText, this.Connection);23
 }
      }24
 else
      else25
 {
      {26
 cmdDeriveCommand = new SqlCommand(this.cmdText, this.Connection);
            cmdDeriveCommand = new SqlCommand(this.cmdText, this.Connection);27
 }
      }28
 cmdDeriveCommand.CommandType = CommandType.StoredProcedure;
      cmdDeriveCommand.CommandType = CommandType.StoredProcedure;29
 cmdDeriveCommand.Parameters.Add(new SqlParameter("@procedure_name", SqlDbType.NVarChar, 0xff));
      cmdDeriveCommand.Parameters.Add(new SqlParameter("@procedure_name", SqlDbType.NVarChar, 0xff));30
 cmdDeriveCommand.Parameters[0].Value = txtCommand[3];
      cmdDeriveCommand.Parameters[0].Value = txtCommand[3];31
 ArrayList parms = new ArrayList();
      ArrayList parms = new ArrayList();32
 try
      try33
 {
      {34
 try
            try35
 {
            {36
 using (SqlDataReader drParam = cmdDeriveCommand.ExecuteReader())
                  using (SqlDataReader drParam = cmdDeriveCommand.ExecuteReader())37
 {
                  {38
 SqlParameter parameter = null;
                        SqlParameter parameter = null;39
 while (drParam.Read())
                        while (drParam.Read())40
 {
                        {41
 parameter = new SqlParameter();
                              parameter = new SqlParameter();42
 parameter.ParameterName = (string) drParam["PARAMETER_NAME"];
                              parameter.ParameterName = (string) drParam["PARAMETER_NAME"];43
 parameter.SqlDbType = MetaType.GetSqlDbTypeFromOleDbType((short) drParam["DATA_TYPE"], (string) drParam["TYPE_NAME"]);
                              parameter.SqlDbType = MetaType.GetSqlDbTypeFromOleDbType((short) drParam["DATA_TYPE"], (string) drParam["TYPE_NAME"]);44
 object len = drParam["CHARACTER_MAXIMUM_LENGTH"];
                              object len = drParam["CHARACTER_MAXIMUM_LENGTH"];45
 if (len is int)
                              if (len is int)46
 {
                              {47
 parameter.Size = (int) len;
                                    parameter.Size = (int) len;48
 }
                              }49
 parameter.Direction = this.ParameterDirectionFromOleDbDirection((short) drParam["PARAMETER_TYPE"]);
                              parameter.Direction = this.ParameterDirectionFromOleDbDirection((short) drParam["PARAMETER_TYPE"]);50
 if (parameter.SqlDbType == SqlDbType.Decimal)
                              if (parameter.SqlDbType == SqlDbType.Decimal)51
 {
                              {52
 parameter.Scale = (byte) (((short) drParam["NUMERIC_SCALE"]) & 0xff);
                                    parameter.Scale = (byte) (((short) drParam["NUMERIC_SCALE"]) & 0xff);53
 parameter.Precision = (byte) (((short) drParam["NUMERIC_PRECISION"]) & 0xff);
                                    parameter.Precision = (byte) (((short) drParam["NUMERIC_PRECISION"]) & 0xff);54
 }
                              }55
 parms.Add(parameter);
                              parms.Add(parameter);56
 }
                        }57
 }
                  }58
 }
            }59
 finally
            finally60
 {
            {61
 cmdDeriveCommand.Connection = null;
                  cmdDeriveCommand.Connection = null;62
 }
            }63
 }
      }64
 catch
      catch65
 {
      {66
 throw;
            throw;67
 }
      }68

69
 if (params.Count == 0)
      if (params.Count == 0)70
 {
      {71
 // throw an exception that current storedprocedure does not exist
            // throw an exception that current storedprocedure does not exist72
 }
      }73
 
      74
 this.Parameters.Clear();
      this.Parameters.Clear();75
 foreach (object parm in parms)
      foreach (object parm in parms)76
 {
      {77
 this._parameters.Add(parm);
            this._parameters.Add(parm);78
 }
      }79
 }
}ADP.ParseProcedureName其实就是获取存储过程命令的细节信息,有兴趣的可以反编译来看看。
纵观整个方法,有效性验证-〉获取命令字符串-〉执行查询-〉填充参数列表-〉返回。应该是非常简洁明朗的,最多也就是在数据库Query的阶段需要有一个来回,其他操作根本就谈不上有什么复杂度,而且也不存在大数据的对象,对性能的损耗谈不上多巨大。
下面来看看FillSchema的处理过程
FillSchema方法
这个部分因为代码比较多,所以我就抽关键的部分来看一下。
首先,FillSchema是DataAdapter类定义的一个方法,而具体实现则是在该类的子类DBDataAdapter中完成的(SqlDataAdapter继承于DBDataAdapter)。
通过反编译,可以发现FillSchema的关键处理步骤是在其调用私有方法FillSchemaFromCommand来完成的。简单看一下该方法体的内容:
 private DataTable[] FillSchemaFromCommand(object data, SchemaType schemaType, IDbCommand command, string srcTable, CommandBehavior behavior)
private DataTable[] FillSchemaFromCommand(object data, SchemaType schemaType, IDbCommand command, string srcTable, CommandBehavior behavior)2
 {
{3
 IDbConnection connection = DbDataAdapter.GetConnection(command, "FillSchema");
      IDbConnection connection = DbDataAdapter.GetConnection(command, "FillSchema");4
 ConnectionState state = ConnectionState.Open;
      ConnectionState state = ConnectionState.Open;5
 DataTable[] arrTables = new DataTable[0];
      DataTable[] arrTables = new DataTable[0];6
 try
      try7
 {
      {8
 try
            try9
 {
            {10
 DbDataAdapter.QuietOpen(connection, out state);
                  DbDataAdapter.QuietOpen(connection, out state);11
 using (IDataReader reader = command.ExecuteReader((behavior | CommandBehavior.SchemaOnly) | CommandBehavior.KeyInfo))
                  using (IDataReader reader = command.ExecuteReader((behavior | CommandBehavior.SchemaOnly) | CommandBehavior.KeyInfo))12
 {
                  {13
 if (reader == null)
                        if (reader == null)14
 {
                        {15
 return arrTables;
                              return arrTables;16
 }
                        }17
 int tblIndex = 0;
                        int tblIndex = 0;18
 while (true)
                        while (true)19
 {
                        {20
 if (0 < reader.FieldCount)
                              if (0 < reader.FieldCount)21
 {
                              {22
 try
                                    try23
 {
                                    {24
 string txtTableName = null;
                                          string txtTableName = null;25
 SchemaMapping mapping = new SchemaMapping(this, reader, true);
                                          SchemaMapping mapping = new SchemaMapping(this, reader, true);26
 if (data is DataTable)
                                          if (data is DataTable)27
 {
                                          {28
 mapping.DataTable = (DataTable) data;
                                                mapping.DataTable = (DataTable) data;29
 }
                                          }30
 else
                                          else31
 {
                                          {32
 mapping.DataSet = (DataSet) data;
                                                mapping.DataSet = (DataSet) data;33
 txtTableName = DbDataAdapter.GetSourceTableName(srcTable, tblIndex);
                                                txtTableName = DbDataAdapter.GetSourceTableName(srcTable, tblIndex);34
 }
                                          }35
 mapping.SetupSchema(schemaType, txtTableName, false, null, null);
                                          mapping.SetupSchema(schemaType, txtTableName, false, null, null);36
 DataTable currentTable = mapping.DataTable;
                                          DataTable currentTable = mapping.DataTable;37
 if (currentTable != null)
                                          if (currentTable != null)38
 {
                                          {39
 arrTables = DbDataAdapter.AddDataTableToArray(arrTables, currentTable);
                                                arrTables = DbDataAdapter.AddDataTableToArray(arrTables, currentTable);40
 }
                                          }41
 }
                                    }42
 finally
                                    finally43
 {
                                    {44
 tblIndex++;
                                          tblIndex++;45
 }
                                    }46
 }
                              }47
 if (!reader.NextResult())
                              if (!reader.NextResult())48
 {
                              {49
 return arrTables;
                                    return arrTables;50
 }
                              }51
 }
                        }52
 }
                  }53
 }
            }54
 finally
            finally55
 {
            {56
 DbDataAdapter.QuietClose(connection, state);
                  DbDataAdapter.QuietClose(connection, state);57
 }
            }58
 }
      }59
 catch
      catch60
 {
      {61
 throw;
            throw;62
 }
      }63
 return arrTables;
      return arrTables;64
 }
}
首先,该操作含有一个数据库的Query操作,这里其实是调用DBDataAdapter的SelectCommand的对象,执行一次查询,然后遍历查询返回的所有表,每遍历到一个表的时候,通过该表的信息实例化一个SchemaMapping对象,再有该对象创建为DataSet/DataTable创建架构信息。
这里,DataSet/DataTable是作为参数提供的,整个处理过程,首先必然的需要完成一次查询操作,由于使用IDataReader,所以在查询之后的所有操作期间,连接是保持着的,这一定程度上占用了一些资源(也可以说这些资源还不算太昂贵);其次,实例化一个SchemaMapping对象(该对象是内部类,我在MSDN上没有查到相关介绍性资料),我简单看了一下这个类的代码,在我看来,它的处理过程应该是占据了整个过程蛮大一部分资源的,这方面属于个人见解。
由于我的认识上的有限,也为了保证文章的内容无误导,暂且说到这里。这个方法的进一步讨论希望留给有兴趣的朋友。
总结
以上是我对这两个方法认识方面简单的一个概括,其实从上面的描述,也打消了我原先认为的这两个方法在获取元数据上有本质的差别。个人认为,之所以获取结构性元数据的消耗大,是因为获取逻辑的繁琐以及使用的对象的庞大,而参数信息相对而言完全属于轻量级的东西,所以所谓性能上的差异并非因为获取机制的本质差异引起的。
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号