Benutzer mit den meisten Antworten
VB Nutzung ADODB unter 2010, Abfrage Field Type Problem

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
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:
Denn ein Recordset ist ein unmanaged Objekt, wo der Zugriff über die COM-Marshalling erfolgen muss -m_RowSource = New_RowSource.Clone locTable.Clear() Dim adotmp As ADODB.Recordset = New ADODB.Recordset adotmp = m_RowSource
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
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
-
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
-
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
-
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
-
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
-
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:
Denn ein Recordset ist ein unmanaged Objekt, wo der Zugriff über die COM-Marshalling erfolgen muss -m_RowSource = New_RowSource.Clone locTable.Clear() Dim adotmp As ADODB.Recordset = New ADODB.Recordset adotmp = m_RowSource
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
-
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
-
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