问题 SQLGetInfo - 如何使用此功能


我开发了一个c#应用程序,它连接到许多类型的数据库服务器,如Sql,Oracle,Mysql等。连接建立使用 ODBC

我需要找到建立连接的服务器类型(DBMS类型),因为用户只输入 DSN 名称。

经过几个小时的冲浪,我发现我的要求只能通过这个功能 的SQLGetInfo但我不知道如何在C#中处理这个问题。

甚至检查过这个 链接1

从上面的链接,我发现ODBC API只给出了 数据库名称 和 数据源名称。但我需要找到Datatbase类型,比如它是SQL连接还是Oracle连接还是mySqlConnection。

是否可以从DSN名称获取DBMS类型..?

注意:我不想从注册表设置中读取它。我试过这个并且它可以工作,但我有权限问题..!


9903
2018-05-20 07:41


起源

问题的关键是什么......我不是这个居高临下的意思。尽管如此,为什么不使用IDBConnection 厂 模式?那样你不需要知道数据库制造商,只需要写一个DAL?除非您正在编写ORM,否则它会针对不同的数据库而产生巨大的开销,并且收益不大 - 尤其是当您没有做任何过于具体的事情时。 EF,Mindscape LightSpeed,Hibernate等都提供了更好的解决方案 - 但即便是这些产品也要求最终用户手动指定数据库类型。 - Jeremy Thompson


答案:


这绝对可以解决问题。这是我的钩子OdbcConnection GetInfoStringUnhandled函数的实现。上帝,我们爱反思, 我知道我是传奇 ;)

public enum SQL_INFO
{
    DATA_SOURCE_NAME,
    DRIVER_NAME,
    DRIVER_VER,
    ODBC_VER,
    SERVER_NAME,
    SEARCH_PATTERN_ESCAPE,
    DBMS_NAME,
    DBMS_VER,
    IDENTIFIER_CASE,
    IDENTIFIER_QUOTE_CHAR,
    CATALOG_NAME_SEPARATOR,
    DRIVER_ODBC_VER,
    GROUP_BY,
    KEYWORDS,
    ORDER_BY_COLUMNS_IN_SELECT,
    QUOTED_IDENTIFIER_CASE,
    SQL_OJ_CAPABILITIES_30,
    SQL_SQL92_RELATIONAL_JOIN_OPERATORS,
    SQL_OJ_CAPABILITIES_20

}

public static string GetInfoStringUnhandled(OdbcConnection ocn, SQL_INFO info)
{
    MethodInfo GetInfoStringUnhandled = ocn.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).First(c => c.Name == "GetInfoStringUnhandled");
    ParameterInfo SQL_INFO =
        GetInfoStringUnhandled.GetParameters()
            .First(c => (c.ParameterType.ToString() == "System.Data.Odbc.ODBC32+SQL_INFO"));
    Array EnumValues = SQL_INFO.ParameterType.GetEnumValues();
    foreach (var enumval in EnumValues) {
        if (enumval.ToString() == info.ToString()) {
            return Convert.ToString(GetInfoStringUnhandled.Invoke(ocn, new object[] { enumval }));
        }
    }
    return string.Empty;
}

private static void Main(string[] args)
{
    OdbcConnection ocn = new OdbcConnection("DSN=GENESIS");
    ocn.Open();
    Console.WriteLine(GetInfoStringUnhandled(ocn, SQL_INFO.DBMS_VER) + " " +
                      GetInfoStringUnhandled(ocn, SQL_INFO.DBMS_NAME));
}

我找到的最好的文档,解释了使用SQLGetInfo的47种可能性 https://mariadb.com/kb/en/sql-99/sqlgetinfo/

尽管如此,OdbcConnection刚刚集成了一个包含19种可能性的枚举。以下是System.Data.Odbc.ODBC32的SQL_INFO的反汇编枚举:

public enum SQL_INFO : ushort
{
    DATA_SOURCE_NAME = (ushort)2,
    DRIVER_NAME = (ushort)6,
    DRIVER_VER = (ushort)7,
    ODBC_VER = (ushort)10,
    SERVER_NAME = (ushort)13,
    SEARCH_PATTERN_ESCAPE = (ushort)14,
    DBMS_NAME = (ushort)17,
    DBMS_VER = (ushort)18,
    IDENTIFIER_CASE = (ushort)28,
    IDENTIFIER_QUOTE_CHAR = (ushort)29,
    CATALOG_NAME_SEPARATOR = (ushort)41,
    DRIVER_ODBC_VER = (ushort)77,
    GROUP_BY = (ushort)88,
    KEYWORDS = (ushort)89,
    ORDER_BY_COLUMNS_IN_SELECT = (ushort)90,
    QUOTED_IDENTIFIER_CASE = (ushort)93,
    SQL_OJ_CAPABILITIES_30 = (ushort)115,
    SQL_SQL92_RELATIONAL_JOIN_OPERATORS = (ushort)161,
    SQL_OJ_CAPABILITIES_20 = (ushort)65003
}

如您所见,您只需使用所需信息的(ushort)转换整数调用GetInfoStringUnhandled方法即可。这是一个例子:

public static string GetInfoStringUnhandled(OdbcConnection ocn, ushort info)
{
    MethodInfo GetInfoStringUnhandled = ocn.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).First(c => c.Name == "GetInfoStringUnhandled");
    return Convert.ToString(GetInfoStringUnhandled.Invoke(ocn, new object[] { (ushort)info }));            
}

public static void Main(string[] args)
{
    OdbcConnection ocn = new OdbcConnection("DSN=GENESIS");
    ocn.Open();
    Console.WriteLine(GetInfoStringUnhandled(ocn, (ushort)10003)); //SQL_CATALOG_NAME returns Y
}

4
2018-05-28 10:31



真的谢谢男人.. !! - Arshad


简短回答: 别。请尽量找到托管等价物。没有记录的获取此句柄的方法。

答案很长: SQLGetInfo函数的InfoType参数有47个可能的值。 参考。您可以检索带引号的标识符的正则表达式模式,如下所示:

DataTable dt = connection.GetSchema(DbMetaDataCollectionNames.DataSourceInformation); 
string quotedIdentifierPattern = (string)dt.Rows[0][DbMetaDataColumnNames.QuotedIdentifierPattern];

这将允许您识别但不生成带引号的标识符。假设引号字符确实是一个字符是安全的,所以你可以通过简单地做到:

Regex.Unescape(quotedIdentifierPattern)[0];

(.Unescape()是必要的,因为引号字符可能是特殊的 regexen因此逃脱了。)

SQLInfo()的大多数其他用途可以类似地使用.GetSchema()来解决。如果 你绝对肯定必须使用SQLGetInfo()来做某事,我建议 使用私有方法 .GetInfoInt16Unhandled().GetInfoInt32Unhandled()..GetInfoStringUnhandled() 上 OdbcConnection 通过反思。这是 但是,如果没有警告就会破裂。

您可以通过私有.ConnectionHandle获取内部句柄 会员,但这同样受到破坏而且不太方便 (因为你必须编写所有非托管互操作代码)。

使用 ILSpy 要么 反光 获得更多 有关实施的详细信息。逆向工程内部可以很多 案例指向完全托管的解决方案。 参考


要么 以此为基础 MSDN样本 用于通过不同命令检测版本的代码,例如

MySQL:“SELECT version()”;
Oracle:“SELECT @@ version,@@ version_comment FROM dual”;
SQLServer:“SELECT @@ version”;

MSDN示例代码:

using System;
using System.Data;

namespace IDbConnectionSample {
   class Program {
      static void Main(string[] args) {
         IDbConnection connection;

         // First use a SqlClient connection
         connection = new System.Data.SqlClient.SqlConnection(@"Server=(localdb)\V11.0");
         Console.WriteLine("SqlClient\r\n{0}", GetServerVersion(connection));
         connection = new System.Data.SqlClient.SqlConnection(@"Server=(local);Integrated Security=true");
         Console.WriteLine("SqlClient\r\n{0}", GetServerVersion(connection));

         // Call the same method using ODBC 
         // NOTE: LocalDB requires the SQL Server 2012 Native Client ODBC driver
         connection = new System.Data.Odbc.OdbcConnection(@"Driver={SQL Server Native Client 11.0};Server=(localdb)\v11.0");
         Console.WriteLine("ODBC\r\n{0}", GetServerVersion(connection));
         connection = new System.Data.Odbc.OdbcConnection(@"Driver={SQL Server Native Client 11.0};Server=(local);Trusted_Connection=yes");
         Console.WriteLine("ODBC\r\n{0}", GetServerVersion(connection));

         // Call the same method using OLE DB
         connection = new System.Data.OleDb.OleDbConnection(@"Provider=SQLNCLI11;Server=(localdb)\v11.0;Trusted_Connection=yes;");
         Console.WriteLine("OLE DB\r\n{0}", GetServerVersion(connection));
         connection = new System.Data.OleDb.OleDbConnection(@"Provider=SQLNCLI11;Server=(local);Trusted_Connection=yes;");
         Console.WriteLine("OLE DB\r\n{0}", GetServerVersion(connection));
         }

      public static string GetServerVersion(IDbConnection connection) {
         // Ensure that the connection is opened (otherwise executing the command will fail)
         ConnectionState originalState = connection.State;
         if (originalState != ConnectionState.Open)
            connection.Open();
         try {
            // Create a command to get the server version 
            IDbCommand command = connection.CreateCommand();
            command.CommandText = "SELECT @@version"; //<- HERE  
            //try out the different commands by passing the CommandText as a parameter
            return (string)command.ExecuteScalar();
         }
         finally {
            // Close the connection if that's how we got it 
            if (originalState == ConnectionState.Closed)
               connection.Close();
         }
      }
   }
}

要么 你可以做一些别人的建议,更优雅一点。

注意:这是关于@FabianStern的答案的复制/粘贴工作 - 对作者有用。我只是减少了程序性和正统性,因为我无法忍受级联的Try-Catch's):

protected static DBType GetDBType(string odbcConnStr)
{
 var dbType = DBType.UNSUPPORTED;
 try
 {
  using (var cn = new OdbcConnection(odbcConnStr))
  {
    if (cn.State != ConnectionState.Open) cn.Open();
    dbType = GetDbType(cn, dbType)
    if (dbType > 0) return dbType;

    var sqlVersionQuery = "SELECT version()"; 
    dbType = GetDbType(cn, sqlVersionQuery, DBType.MYSQL)
    if (dbType > 0) return dbType;

    sqlVersionQuery = "SELECT @@version, @@version_comment FROM dual"; 
    dbType = GetDbType(cn, sqlVersionQuery, DBType.Oracle)
    if (dbType > 0) return dbType;

    sqlVersionQuery = "SELECT @@version"; 
    dbType = GetDbType(cn, sqlVersionQuery, DBType.MSSQL)
    if (dbType > 0) return dbType;
  }
 }
 catch(Exception connEx) { }
 return dbType;
}

public enum DBType
{
    UNSUPPORTED = 0,
    MYSQL = 1,
    ORACLE = 2,
    MSSQL = 3,
    JET = 4
}

private static DBType GetDBType(OdbcConnection cn, DBType dbType)
{
  try
  {
    if (cn.Driver == "odbcjt32.dll") dbType = DBType.JET;
  }
  catch(Exception ex) { }
  return dbType;
}

private static DBType GetDbType(OdbcConnection cn, string sqlVersionQuery, DBType dbType)
{
  try
  {
  using (var cmd = cn.CreateCommand()) {
  cmd.CommandText = sqlVersionQuery;
    try
    {
       using (var reader = cmd.ExecuteReader()) 
       {
           if (reader.HasRows) return dbType;
       }
    }
    catch (Exception ex) { }
  }}
  catch (Exception cmdEx) { }           
  }
 return dbType;
}

4
2018-05-25 14:16



你混淆了Oracle和mySQL查询字符串,复制和粘贴更优雅;) - Fabian Stern


您是否尝试解析OdbcConnection的.Driver属性? 它将显示连接使用的数据库包装驱动程序。 您也可以在HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ ODBC的注册表中找到这些映射

有时它只是一个.dll名称(例如,对于Microsoft Excel驱动程序),但它可以给你一个提示。

您还可以执行特定于数据库的命令以从服务器获取数据库版本,如下所示:

   static void Main(string[] args)
    {
        var cn = new OdbcConnection("DSN=mysql1");
        Console.WriteLine("DBType: {0}", GetDbType(cn));
        Console.Read();
    }

    public enum DbType
    {
        UNSUPPORTED = 0,
        MYSQL = 1,
        ORACLE = 2,
        MSSQL = 3,
        POSTGRESQL = 4,
        JET = 5
    }

    public static DbType GetDbType(OdbcConnection cn)
    {
        DbType t = DbType.UNSUPPORTED;
            try
            {
                if (cn.State != ConnectionState.Open) cn.Open();

                if (cn.Driver == "odbcjt32.dll")
                {
                    return DbType.JET;
                }

                var cmd = cn.CreateCommand();
                string outstring = "";
                cmd.CommandText = "SELECT * FROM v$version";
                try
                {
                    var reader = cmd.ExecuteReader();
                    if (reader.HasRows)
                    {
                        reader.Read();
                        outstring = String.Format("{0}", reader.GetString(0));

                    }
                }
                catch (Exception)
                {
                    cmd = cn.CreateCommand();
                    cmd.CommandText = "SELECT @@version, @@version_comment FROM dual";
                    try
                    {
                        var reader = cmd.ExecuteReader();
                        if (reader.HasRows)
                        {
                            reader.Read();
                            outstring = String.Format("{0} {1}", reader.GetString(0), reader.GetString(1));

                        }
                    }
                    catch (Exception)
                    {
                        cmd = cn.CreateCommand();
                        cmd.CommandText = "SELECT @@version";
                        try
                        {
                            var reader = cmd.ExecuteReader();
                            if (reader.HasRows)
                            {
                                reader.Read();
                                outstring = String.Format("{0}", reader.GetString(0));

                            }
                        }
                        catch (Exception)
                        {
                            cmd = cn.CreateCommand();
                            cmd.CommandText = "SELECT version()";
                            try
                            {
                                var reader = cmd.ExecuteReader();
                                if (reader.HasRows)
                                {
                                    reader.Read();
                                    outstring = String.Format("{0}", reader.GetString(0));

                                }
                            }
                            catch (Exception)
                            {
                            }
                        }
                    }
                }

                outstring = outstring.ToUpper();

                if (outstring.Contains("MYSQL"))
                {
                    t = DbType.MYSQL;
                }
                else if (outstring.Contains("ORACLE"))
                {
                    t = DbType.ORACLE;
                }
                else if (outstring.Contains("SQL SERVER"))
                {
                    t = DbType.MSSQL;
                }
                else if (outstring.Contains("POSTGRESQL"))
                {
                    t = DbType.POSTGRESQL;
                }


            }
            catch (Exception E)
            {

            }
        return t;

    }

1
2018-05-25 11:15



从驱动程序我们只能得到Dll名称而不是DBMS类型或驱动程序名称@Fabian Stern ....在该注册表中我们也不会得到DBMS类型。我已经检查过了。!我需要找到SQL,MySQL,Oracle等服务器的类型 - Arshad
实际上没有类型。你可以拥有的是驱动程序.dll,这也是所使用的DBMS的类型。这是我唯一知道的可能性。另一种方法是为每个返回各自版本的数据库系统“Oracle”,“MSSQL”,“mySQL”执行初始字符串。我现在将继续努力并给你一个反馈配偶 - Fabian Stern
我无法得到这个另一种方法是为每个数据库系统“Oracle”,“MSSQL”,“mySQL”执行返回各自版本的初始字符串...... !!无论如何都会等待你的反馈 - Arshad
你能给我一个样品吗? - Arshad
.NET Driver属性实际上与SQLGetInfo的SQL_DRIVER_NAME参数名称匹配。就像Fabian提出的那样,我认为没有比DBMS的地图驱动更好的解决方案了。与给定DBMS的连接确实是驱动程序。 - Simon Mourier