Pytanie SQLGetInfo - Jak korzystać z tej funkcji


Stworzyłem aplikację c #, która łączy się z wieloma typami serwerów baz danych, takich jak Sql, Oracle, Mysql itp. Połączenie zostało ustanowione przy użyciu ODBC.

Muszę znaleźć typ serwera (typ DBMS), dla którego ustanowiono połączenie, ponieważ użytkownik tylko wchodzi do DSN Nazwa.

Po kilku godzinach surfowania odkryłem, że moje wymagania są możliwe tylko dzięki tej funkcji SQLGetInfoAle nie wiem jak sobie z tym poradzić w C #.

nawet to sprawdziłem Link1.

z powyższego linku wynika, że ​​API ODBC daje tylko Nazwa bazy danych i Nazwa źródła danych.Ale potrzebuję znaleźć typ bazy danych, np. Czy jest to połączenie SQL, czy połączenie Oracle lub mySqlConnection.

Czy można uzyskać typ DBMS z nazwy DSN ..?

Uwaga: Nie chcę odczytać go z ustawienia rejestru. Próbowałem tego i działa, ale mam problemy z uprawnieniami ..!


11
2018-05-20 07:41


pochodzenie


O co chodzi z pytaniem ... Nie mam na myśli tego protekcjonalnego. Z całym szacunkiem, dlaczego nie używasz IDBConnection Fabryka wzór? W ten sposób nie musisz znać producenta DB i musisz tylko napisać jedną DAL? O ile nie piszesz ORM-a, jest to ogromna inwestycja skierowana na różne bazy danych i niezbyt duży zysk - zwłaszcza gdy nie robisz nic zbyt konkretnego. EF, Mindscape LightSpeed, Hibernate, itp. Oferują lepsze rozwiązania - ALE nawet te produkty wymagają od użytkowników ręcznego określenia typu bazy danych. - Jeremy Thompson


Odpowiedzi:


To całkowicie załatwi sprawę. Oto moja implementacja do podłączenia funkcji OdbcConnection GetInfoStringUnhandled. Bogu, kochamy odbicie, Wiem, że jestem legendą ;)

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));
}

Najlepszą dokumentację, którą znalazłem, wyjaśniającą 47 możliwości korzystania z SQLGetInfo jest tutaj https://mariadb.com/kb/en/sql-99/sqlgetinfo/

Niemniej jednak OdbcConnection właśnie zintegrował enum z 19 możliwościami. Poniżej znajduje się zdemontowane wyliczenie SQL_INFO z System.Data.Odbc.ODBC32:

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
}

Jak widzisz, możesz po prostu wywołać metodę GetInfoStringUnhandled z (ushort) liczbą całkowitą z potrzebnych informacji. To jest przykład:

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



Naprawdę dzięki człowieku .. !! - Arshad


Krótka odpowiedź: Nie rób tego. Zamiast tego bardzo trudno znaleźć zarządzane odpowiedniki. Nie ma udokumentowanego sposobu uzyskania tego uchwytu.

Długa odpowiedź: Parametr InfoType funkcji SQLGetInfo ma 47 możliwych wartości. Ref. Możesz pobrać wzorzec regex dla cudzysłowów w następujący sposób:

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

Umożliwi to rozpoznanie, ale nie wygenerowanie cytowanych identyfikatorów. Można bezpiecznie założyć, że cytowana postać jest rzeczywiście jedną postacią, więc możesz ją zdobyć po prostu:

Regex.Unescape(quotedIdentifierPattern)[0];

(.Unescape () jest niezbędna, ponieważ znak cudzysłowu może być specjalny regexen, a tym samym uciekł.)

Większość innych zastosowań SQLInfo () można podobnie rozwiązać za pomocą .GetSchema (). Gdyby absolutnie, pozytywnie musisz użyć SQLGetInfo () dla czegoś, polecam używając prywatnych metod .GetInfoInt16Unhandled(), .GetInfoInt32Unhandled() i ..GetInfoStringUnhandled() na OdbcConnection przez odbicie. To jest może jednak ulec złamaniu bez ostrzeżenia.

Możesz uzyskać wewnętrzny uchwyt przez prywatną .ConnectionHandle członka, ale to również podlega złamaniu i znacznie mniej wygodne (ponieważ musisz napisać cały niezarządzalny kod interop).

Posługiwać się ILSpy lub Reflektor aby uzyskać więcej szczegóły dotyczące realizacji. Inżynieria odwrotna wnętrzności może w wielu przypadkach przypadki wskazują na w pełni zarządzane rozwiązanie. Ref.


LUB zbuduj na tym Próbka MSDN kod do wykrywania wersji za pomocą różnych poleceń, np

MySQL: "SELECT version ()";
Oracle: "SELECT @@ wersja, @@ version_comment FROM dual";
SQLServer: "SELECT @@ version";

MSDN Sample Code:

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();
         }
      }
   }
}

LUB możesz zrobić coś, co sugerują inni, z odrobiną elegancji.

Uwaga: jest to zadanie kopiowania / wklejania w odpowiedzi @FabianStern - kredyt dla autora. Zrobiłem to mniej proceduralne i bardziej ortodoksyjne, ponieważ nie mogłem znieść kaskadowego Try-Catcha):

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



pomieszałeś ciągi zapytań Oracle i mySQL, skopiuj i wklej z większą elegancją;) - Fabian Stern


Czy próbowałeś parsować właściwość .Driver OdbcConnection? Wyświetli to używany sterownik opakowania bazy danych dla połączenia. Te mapowania można znaleźć również w rejestrze na HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ ODBC

Czasem jest to po prostu nazwa .dll (np. Dla sterownika Microsoft Excel), ale może dać ci wskazówkę.

Można również wykonywać komendy specyficzne dla bazy danych w celu uzyskania wersji bazy danych z serwera, jak na przykład:

   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



Od sterownika możemy uzyskać tylko nazwę Dll, a nie typ DBMS lub nazwę sterownika @Fabian Stern .... A także w tym rejestrze nie dostaniemy typu DBMS. Już sprawdziłem z tym.! I muszę znaleźć typ serwera, taki jak SQL, MySQL, Oracle itp - Arshad
W rzeczywistości nie ma typu. To, co możesz mieć, to sterownik .dll i jest to również typ używanego systemu DBMS. To jedyna możliwość, którą znam. Innym podejściem byłoby wykonanie początkowych łańcuchów dla każdego systemu baz danych "Oracle", "MSSQL", "mySQL", które zwracają odpowiednie wersje. Będę pracował nad tym teraz i dam ci kolegę od opinii - Fabian Stern
Nie mogłem tego uzyskać Innym podejściem byłoby wykonanie początkowych łańcuchów dla każdego systemu baz danych "Oracle", "MSSQL", "mySQL", które zwracają odpowiednie wersje ... !! Na pewno poczekam na Twoją opinię - Arshad
Czy mógłbyś podać mi próbkę? - Arshad
Właściwość sterownika .NET faktycznie odpowiada nazwie parametru SQL_DIVIVER_NAME SQLGetInfo. Jak sugeruje Fabian, myślę, że nie ma lepszego rozwiązania niż sterownik map dla DBMS. Połączenie z danym DBMS jest rzeczywiście sterownikiem. - Simon Mourier