none
VB Nutzung ADODB unter 2010, Abfrage Field Type Problem RRS feed

  • Frage

  • Hallo zusammen,

    ich bereite gerade eine Umstellung einer alten VB6 App vor, und würde gerne die DB Zugriffe erst einmal in der alten ADODB Zugriffsyntax der Actice X Data Objekts 2.8 Library erhalten.

    Habe ein eigenes UserControl, wo ich ein ADODB.Recordset als Schnittstelle übergebe.

    Beim Zugriff auf rec.FIELDS(0).TYPE bekomme ich folgenden Fehler:

    Fehler: Die Methode "instance valuetype ADODB.DataTypeEnum [UControlsLib] ADODB.Field::get_Type()" der Klasse "ADODB.InternalField" ist nicht vorhanden.-GetAdoColType

    Im Direktfenster kann ich Debug.print(rec.fields(0).type) jedoch ausführen und liefert mir z.B. 200 zurück (also den integer EnumWert wie erwartet)

    Habe den verweis auf die adodb.dll eingebaut, führt aber zu weiteremFehler, das ich das Connectionobjekt ADODB.Connection nicht mehr verwenden kann, und ADODB nur noch ein namspace ist...

    Gleicher fehler übrigens mit rec.Fields.Item(0).type

    Public Function GetAdoColType(ByVal AdoRec As ADODB.Recordset, ByVal FieldName As String) As Integer
        On Error GoTo err_GetAdoColType
        Dim iTemp As ADODB.DataTypeEnum
        iTemp = AdoRec.Fields(FieldName).Type
        Return iTemp
    exit_GetAdoColType:
        Exit Function
    err_GetAdoColType:
        Debug.Print(Err.Description & "-GetAdoColType")
        Resume exit_GetAdoColType
      End Function
    

    Kann hier jemnad einen Tipp geben, den alle DB Construkte auf .Net Syntax umzuschreiben erfordert erheblich mehr Umstellungszeiten.

    Gruß Ingo

    Montag, 2. Mai 2011 16:21

Antworten

  • Hallo Ingo,

    da man aus Deinem Code nicht erkennen konnte, wie das RecordSet reinkommt hatte ich mich auf den Satz beschränkt:

    Etwas wie Clone funktioniert mit COM-Objekten nicht wirklich.

    was sich bezog auf:

       m_RowSource = New_RowSource.Clone
       locTable.Clear()
       Dim adotmp As ADODB.Recordset = New ADODB.Recordset
       adotmp = m_RowSource
    
    Denn ein Recordset ist ein unmanaged Objekt, wo der Zugriff über die COM-Marshalling erfolgen muss -
    was schon in einfacheren Fällen komplex ist, für ADODB Objekte kaum möglich.

    Um nach vorne zu schauen:
    Nicht nur im Interesse einer späteren Anpassung an aktuellere Datenzugriffstechniken
    solltest Du die RecordSets und Connections aus den Steuerelementen verbannen.

    Üblicherweise lagert man sämtliche Datenzugriffe aus und lässt die Steuerelementen
    nur mit den Daten arbeiten, in Deinem Falle dann eine DataTable oder DataSet.

    An Deiner Stelle würde ich mich an die TableAdapter anlehnen
    und alle Datenzugriffe (Lesen aber auch Aktualisierungen) darüber abwickeln.
    Wenn Du dort ein wenig planst, so kannst Du später (zumindest für die wichtigeren Teile)
    ADODB durch ADO.NET austauschen.

    Gruß Elmar

     

    • Als Antwort markiert BIngo2065 Freitag, 6. Mai 2011 15:24
    Freitag, 6. Mai 2011 14:35
    Beantworter

Alle Antworten

  • Hallo Ingo,

    ich kann das hier ohne weitere Angaben nicht nachvollziehen.

    Google spuckt zwar diverse Fehler mit ähnlicher Signatur aus,
    dort wird aber i. a. der Typ nicht korrekt ausgewertet.

    Wie hast Du ADODB als Verweis eingebunden?
    Und wie erzeugst Du das Recordset?

    Ein Codeschnipsel der ohne Probleme (mit Jet und SQL Server) funktioniert:

    Option Explicit On
    Option Infer On
    Option Strict On
    
    Public Class Form1
    
      Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        Dim cnn As ADODB.Connection
        Dim rst As ADODB.Recordset
    
        cnn = New ADODB.Connection
        With cnn
          .CursorLocation = ADODB.CursorLocationEnum.adUseClient
          .ConnectionString = "Provider=SQLOLEDB.1;Data Source=.\SQL2008;Initial Catalog=Northwind;Integrated Security=SSPI;"
          ' .Provider = "Microsoft.Jet.OLEDB.4.0"
          ' .Properties("Data Source").Value = "F:\Newsgroup\ACCESS\A00.MDB"
          .Open()
        End With
        rst = New ADODB.Recordset
        rst.Open("SELECT * FROM dbo.Products", cnn, ADODB.CursorTypeEnum.adOpenStatic, ADODB.LockTypeEnum.adLockOptimistic)
    
        For Each field As ADODB.Field In rst.Fields
          Console.WriteLine("Field: {0}: {1} ({2})", field.Name, field.Type, GetAdoColType(rst, field.Name))
        Next
    
        Do Until rst.EOF
          Console.WriteLine("{0}: {1}", rst(0).Value, rst(1).Value)
          rst.MoveNext()
        Loop
        rst.Close()
        cnn.Close()
      End Sub
    
    
      Public Function GetAdoColType(ByVal AdoRec As ADODB.Recordset, ByVal FieldName As String) As Integer
        On Error GoTo err_GetAdoColType
        Dim iTemp As ADODB.DataTypeEnum
        iTemp = AdoRec.Fields(FieldName).Type
        Return iTemp
    exit_GetAdoColType:
        Exit Function
    err_GetAdoColType:
        Debug.Print(Err.Description & "-GetAdoColType")
        Resume exit_GetAdoColType
      End Function
    
    
    End Class
    
    

    Wenn Deine Anwendung noch einiges an Lebenszeit vor sich hat, solltest Du jedoch über ein Neuschreiben nachdenken.

    Gruß Elmar

    Dienstag, 3. Mai 2011 19:12
    Beantworter
  • Hallo Elmar,

    es findet innerhalb eines User Control statt der aus einer Textbox, einem Button und einem Datagridcontrol besteht.
    Die Erkenntnis, das es prinziepiell geht habe ich auch schon gewonnen, nur innerhalb des user Control geht es leider nicht. Habe folgendes schon getetet:
    a) wie von MS empfohlen, ADODB Recordset als Property an usercontrol übergeben, funktioniert auch, dann dataset, und datatable erzeugt, oledbconnector.fill verwendet, in datatable wurde die Struktur übernommen jedoch mit 0 records. Ich wollte dann den dataTable ans DataGridControl binden.
    Gleicher Code innerhalb eines normalen UserForm geht ohne Probleme.
    b) Prozedur geschrieben wo ich die Daten aus dem ADODB.Recordset selber an den Datatable übergebe, Zeilenweise die Felder zu füllen, dabei brauche ich aus dem ADO jedoch die Information aus dem Feldtyp, deshalb mein Forum eintrag. Ich wollte nur nicht so viel Code posten, zwecks Einhaltung der Forumsregleln (kurzfassen)
    Anbei mal die Property und den abgespeckten Change Handler aus dem User Control zu a)

    Public Property RowSource() As ADODB.Recordset
        Get
          RowSource = m_RowSource
        End Get
        Set(ByVal New_RowSource As ADODB.Recordset)
          If Not IsNothing(New_RowSource) Then
            m_RowSource = New_RowSource 
          End If
          PropChEvArg = New PropertyChangedEventArgs("RowSource")
          UserControl_OnDataPropertyChanged(DataGridCombo, PropChEvArg)
        End Set
      End Property
    
      Private Sub UserControl_OnDataPropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs)
        Select Case cnull(e.PropertyName, "")
          Case "AutoCompletion"
          Case "Headline"
          Case "Alignment"
          Case "MatchRequired"
          Case "Enabled"
          Case "TReadOnly"
          Case "Font"
          Case "ForeColor"
          Case "BackColor"
            Debug.Print("BackColor_Changes")
          Case "TextBoxWidth"
          Case "TextBoxHeight"
          Case "ListWidth"
          Case "ListHeight"
          Case "RowSource"
            If flagInit = True Then
              DataGridCombo.DataSource = Nothing
              m_RowSource = New_RowSource.Clone
              locTable.Clear()
              Dim adotmp As ADODB.Recordset = New ADODB.Recordset
              adotmp = m_RowSource
              Dim locTab1 As New DataTable("TmpTab2")
              Dim ds2 As DataSet
              ds2.Tables.Add(locTab1)
              Dim adap1 As New OleDb.OleDbDataAdapter
              adap1.Fill(locTab1, m_RowSource)
              daAdapter.Fill(locTable, m_RowSource)
              adotmp.Close()
              adotmp = Nothing
              DataGridCombo.DataSource = locTable
            End If
          Case "Text"
          Case "ColumnFormat"
          Case Else
            Debug.Print("Unbekannte Eigenschaft im EventHandler")
        End Select
      End Sub
    
    

    hier ein nicht fertiger Ansatz zu b) , da bin ich dann mit dem Typproblem hängengeblieben

    Friend Function dataTableFillFromAdo(ByRef adorec As ADODB.Recordset) As DataTable
        On Error GoTo err_dataTableFillFromAdo
        Dim tmpTable As DataTable = New DataTable("TmpTable1")
        Dim ds1 As DataSet = New DataSet
        'Dim daAdap1 As OleDb.OleDbDataAdapter = New OleDb.OleDbDataAdapter
        Dim adoCol As Object
        Dim strColName As String
        Dim strColType As String
    
        If Not IsNothing(adorec) Then
          ds1.Tables.Add(tmpTable)
          'daAdap1.Fill(tmpTable, adorec) geht ja leider nicht
          'Spalten anlegen mit richtiger Typzuweisung
          For Each adoCol In adorec.Fields
            strColName = adoCol.Name
            'strColType = getSystemTypeForAdo(CInt(adorec.Fields.Item(adoCol.name).Type))
            strColType = GetSystemTypeForAdo(GetAdoColType(adorec, adoCol.name))
            tmpTable.Columns.Add(strColName, Type.GetType(strColType))
            'tmpTable.Columns.Add(adoCol.Name, Type.GetType(getSystemTypeForAdo(adorec.Fields.Item(adoCol.name).Type)))
    
          Next
          Do Until adorec.EOF
            'Datenfelder der beiden recordsets zuweisen
            'tmpTable.ImportRow(adorec.GetRows)
            '          tmpTable.Columns(adorec.Fields(adoCol).Name) = adorec.Fields(adoCol).OriginalValue
            adorec.MoveNext()
          Loop
        End If
        Return tmpTable
    
    exit_dataTableFillFromAdo:
        Exit Function
    
    err_dataTableFillFromAdo:
        messageBox.show(Err.Description)
        Resume Next
      End Function

    Vielen Dank, das Du mal drauf geschaut hast.

    Ich hoffe das die Situation jetzt klarer ist, und bis auf die Übergabe ADODB ist hier auch alles schon .NET in dem User Control.
    Ich werde hier sicher das eine oder andere Problem noch einspeisen, da von ca. 100.000 Codezeilen erst ca. 10% hinter mir in fetser .NET basis liegen.

    Gruß

    Ingo

    Mittwoch, 4. Mai 2011 14:37
  • Hallo Ingo,

    der DataAdapter ist schlau genug, um sich die Informationen selbst zu besorgen.
    Ein Beispiel (Datenquelle ist hier die Northwind.Products wie gestern):

      Private Sub DataTableFromRecordSet()
        Dim cnn As ADODB.Connection
        Dim rst As ADODB.Recordset
        cnn = New ADODB.Connection
        With cnn
          .CursorLocation = ADODB.CursorLocationEnum.adUseServer
          .ConnectionString = "Provider=SQLOLEDB.1;Data Source=.\SQL2008;Initial Catalog=Northwind;Integrated Security=SSPI;"
          .Open()
        End With
        rst = New ADODB.Recordset
        ' ForwardOnly/ReadOnly genügt...
        rst.Open("SELECT * FROM dbo.Products", cnn, ADODB.CursorTypeEnum.adOpenForwardOnly, ADODB.LockTypeEnum.adLockReadOnly)
    
        Dim table = DataTableFromRecordSet(rst, "Products")
    
        rst.Close()
        cnn.Close()
    
    
        Console.WriteLine("Columns: {0}", table.Columns.Count)
        For Each column As DataColumn In table.Columns
          Console.WriteLine("{0} ({1})", column.ColumnName, column.DataType)
        Next
    
        Console.WriteLine("Rows: {0}", table.Rows.Count)
        For Each row As DataRow In table.Rows
          Console.WriteLine("{0} - {1}", row("ProductID"), row("ProductName"))
        Next
      End Sub
    
      Public Shared Function DataTableFromRecordset(ByVal recordset As ADODB.Recordset, Optional ByVal tableName As String = "") As DataTable
        Dim table As New DataTable()
    
        If Not String.IsNullOrEmpty(tableName) Then
          table.TableName = tableName
        End If
        Dim adapter As New OleDb.OleDbDataAdapter()
        adapter.MissingSchemaAction = MissingSchemaAction.AddWithKey
        adapter.MissingMappingAction = MissingMappingAction.Passthrough
        adapter.Fill(table, recordset)
        Return table
      End Function
    
    
    

    Die DataTableFromRecordSet Methode sollte für die meisten Fälle ausreichen.

    Achte darauf, dass Du die ADODB Objekte (Recordset, Connection) schliesst, sonst hast Du schnell Speicherlecks.
    Etwas wie Clone funktioniert mit COM-Objekten nicht wirklich.

    Gruß Elmar

     

    Mittwoch, 4. Mai 2011 16:35
    Beantworter
  • Hallo Elmar,

    danke für Deinen Tipp, bis auf die Aktivitäten adapter.missing... sah mein versuch ja sehr ähnlich aus.
    Mein Problem ist weiterhin, das der Code in einem normalen Userform funktioniert, und sowie ich die ganze Aktion ins userControl implementiere liefert die FILL Methode die Column Struktur zurück mit 0 Records. Im ADO-Recordset werden nur 3 Stringfelder übergeben, also vollkommen trivial. Der Debug zeigt auch, das die Übergabe des ADODB.Recordset ans User Control funktioniert, der liefert vor dem Aufruf der Zeile
    adap1.Fill(locTab1, m_RowSource)
    noch 119 als Recordcount.
    In dem Punkt bin ich noch nicht weitergekommen.

    Gruß Ingo

    

    Freitag, 6. Mai 2011 12:43
  • Hallo Elmar,

    ich bin ein Stück weiter gekommen, wenn ich innerhalb des User Control auch komplett die Connection erstelle und die SQL-Daten lese, dann geht adapter.fill auch innerhalb des user Control. Es scheint so zu sein, das die Fill Methode das Connection Objekt benötigt um die Daten aus dem ADODB.Recordset zu lesen, wahrscheinlich kommt er deshalb zwar mit den Fields und Field Types richtig raus, kann jedoch nicht auf die Daten zugreifen. Versteh ich zwar noch nicht ganz, da er die ja vorher im UserForm schon gelesen und damit im RecordsetObjekt hat, aber nun ja. Die Forschung geht weiter.

    Gruß

    Ingo

    Freitag, 6. Mai 2011 13:34
  • Hallo Ingo,

    da man aus Deinem Code nicht erkennen konnte, wie das RecordSet reinkommt hatte ich mich auf den Satz beschränkt:

    Etwas wie Clone funktioniert mit COM-Objekten nicht wirklich.

    was sich bezog auf:

       m_RowSource = New_RowSource.Clone
       locTable.Clear()
       Dim adotmp As ADODB.Recordset = New ADODB.Recordset
       adotmp = m_RowSource
    
    Denn ein Recordset ist ein unmanaged Objekt, wo der Zugriff über die COM-Marshalling erfolgen muss -
    was schon in einfacheren Fällen komplex ist, für ADODB Objekte kaum möglich.

    Um nach vorne zu schauen:
    Nicht nur im Interesse einer späteren Anpassung an aktuellere Datenzugriffstechniken
    solltest Du die RecordSets und Connections aus den Steuerelementen verbannen.

    Üblicherweise lagert man sämtliche Datenzugriffe aus und lässt die Steuerelementen
    nur mit den Daten arbeiten, in Deinem Falle dann eine DataTable oder DataSet.

    An Deiner Stelle würde ich mich an die TableAdapter anlehnen
    und alle Datenzugriffe (Lesen aber auch Aktualisierungen) darüber abwickeln.
    Wenn Du dort ein wenig planst, so kannst Du später (zumindest für die wichtigeren Teile)
    ADODB durch ADO.NET austauschen.

    Gruß Elmar

     

    • Als Antwort markiert BIngo2065 Freitag, 6. Mai 2011 15:24
    Freitag, 6. Mai 2011 14:35
    Beantworter
  • Hallo Elmar,

    nach allen Vorteilen die im Konzept der ADO.NET Klassen verborgen sind ist sicher das Ziel komplett auf .NET zu gehen. Es war einfach nur erst einmal eine Analyse welcher Weg der Portierung hier sinnvoll ist, eben die Grundüberlegung ADO noch zu verwenden und nur die Schnittstelle zu den Controls zu aktualisieren, oder eben den harten und aufwendigeren Weg auch die Komplette DB Zugriffseben anzupassen.
    Letzteres werden wir hier wohl tun.

    Vielen Dank bis dahin erst einmal, es ist eben ganz gut sich mal kurz auszutauschen.


    Eine letzte kurze Frage, da wir sowohl MS-SQL als auch Oracel Syntax unterstützen, habe ich es in den Migration Sheets richtig verstanden bezüglich Performance für MSSQL sqlAdapter und für Oracle OleDBAdapter mit Oralce ClientDriver zu verwenden, da der integrierte OracleDriver ja in zukünftigen Versionen abgkündigt ist.

    Gruß Ingo

    Freitag, 6. Mai 2011 15:22
  • Hallo Ingo,

    Wenn ihr mehrere Datenbanken unterstützt, sollte ihr den Datenzugriff auf jeden Fall abtrennen,
    denn so monolithisch wie ADODB es darstellte (und der Ärger später kam ;-)  ist das bei ADO.NET generell nicht gelöst.

    Wenn ihr den Zugriff über eine Schnittstelle (Interface) macht, so könnt ihr den jeweiligen Provider
    vor dem Rest des Programms verbergen.

    Für Oracle gibt es einen ODP.NET Treiber, den man (anstatt ODBC/OleDb) bevorzugen sollte:
    http://www.oracle.com/technetwork/topics/dotnet/index-085163.html

    (und der Grund für die Abkündigung seitens Microsoft ist, deren OracleClient als Übergangslösung zu sehen ist).

    Gruß Elmar

    Freitag, 6. Mai 2011 15:30
    Beantworter
  • Hallo Elmar,

    habe mir den ODAC und die Tools für .NET mal runtergeladen, sieht erfolgversprechend aus, werde die Schiene als nächstes testen. So langsam formt sich das Mirationskonzept. Danke für den Tipp.

    Gruß

    Ingo

    Freitag, 6. Mai 2011 16:09