'Open a recordset in VS2017 like I could in VB6 for MS Access table

I am a long time VB6 guy, and feel squeezed into VS2017

I need your help with a VS2017 equivlent of

dim db as database
dim rs as recordset

db=opendatabase("path to .MDB")

rs = db.openrecordset("select * where myfield ="mine", order by fieldage")  'This SQL bit is easy.

'Then rs.movenext or rs.moveprevious etc etc

'Also it would help if I could say Textbox1.text = rs(3)

This is to be rolled out to more than one PC, so having to set a specific data connection in the PC config is not practical.

Thanks for reading this far.



Solution 1:[1]

I don't know if this would tick off all your needs, but it's longer than a comment, so I'm surfacing it as a possible solution.

It looks like you're doing VB.net? I encountered a similar challenge when I converted directly from VB6 -> C# using a tool I wrote and then made public.

https://github.com/bhoogter/VB6TocSharp (Yes, I wrote this. Yes, it's free).

To get around it, we used the standard System.Data and System.Data.OleDb packages. These did not have the convenience methods provided like .MoveNext() or .MovePrevious(), and also, as you pointed out, could not be referenced by rs(3), let alone some of the easy positioning and filtering we were converting from.

That said, we chose to wrap the objects returned from the database calls with a Recordset class, and use those provide the interface we wanted.

Of course, the original was written in C#, available here:

https://github.com/bhoogter/VB6TocSharp/blob/master/extras/Recordset.cs

But, I put together a VB.NET port of the same, if intersted, linked, and also included here.

https://github.com/bhoogter/VB6TocSharp/blob/master/extras/vb.net/recordset.vb

Of note is the use of a few sub-classes contained within the Recordset class that can make life easier. But, what is available right away is the methods you mentioned (MoveNext, MovePrevious, RS('fieldname'))... Note that in VB6, RS(f) would return a Field object, and the default property of that field object was .Value. This tries to maintain back-wards compatibility by bypassing the Field object and just returning .Fields[i].Value, so you don't have to change your existing code. This saves time and effort in conversion. And, if there is some interface you're missing, you control this layer so you can add and/or modify as suits your conversion needs.

The VB.net ported version is as follows... It is a conversion of the C# one, so I can't guarantee it's 100% perfect, but it should be enough to demonstrate the point. YMMV.

Imports System.Data
Imports System.Data.OleDb

' Recordset object.  Used to wrap other data objects to 'simulate' VB6.

Public Class Recordset
    Public Source As String = ""
    Public Parameters As Dictionary(Of Object, Object)
    Public Database As String = ""
    Public QuietErrors As Boolean = False

    Private mAddingRow As Boolean = False
    Public ReadOnly Property AddingRow As Boolean
        Get
            Return mAddingRow
        End Get
    End Property

    Private connection As OleDbConnection
    Private adapter As OleDbDataAdapter
    Private table As DataTable
    Private filteredTable As DataTable
    Private mFilter As String

    Public Sub New()

    End Sub
    Public Sub New(table As DataTable, adapter As OleDbDataAdapter, connection As OleDbConnection)
        Me.connection = connection
        Me.adapter = adapter
        Me.table = table
    End Sub

    Public Sub New(SQL As String, File As String, Optional QuietErrors As Boolean = False, Optional Parameters As Dictionary(Of Object, Object) = Nothing)
        Me.Source = SQL
        Me.Parameters = Parameters
        Me.Database = File
        Me.QuietErrors = QuietErrors

        Open()
    End Sub

    Public Sub Close()
        Try
            connection?.Close()
        Catch
            ' just suppress
        End Try

        connection = Nothing
        adapter = Nothing
        table = Nothing
        filteredTable = Nothing
    End Sub

    Public Shared Sub sqlExecutionError(mSQL As String, e As Exception)
        Dim T As String = ""
        T &= "getRecordSet Failed: " & e.Message & vbCrLf
        T &= vbCrLf
        T &= mSQL & vbCrLf
        T &= vbCrLf
        T &= "ERROR:" & e.Message

        T = T.Replace("$EDESC", e.Message)
        'ErrMsg = Replace(ErrMsg, "$ENO", Err().Number)
        T = T.Replace("$ESRC", e.Source)
        MsgBox("Database Error: " + T, 0, "Error")
        'CheckStandardErrors() ' Bookmark/updateable query
    End Sub

    Private Function ConnectionString(file As String) As String
        Return "PROVIDER=Microsoft.Jet.OLEDB.4.0Data Source=" + file
    End Function

    Public Property AbsolutePosition As Integer = -1
    Public Property Position As Integer
        Get
            Return AbsolutePosition
        End Get
        Set(value As Integer)
            AbsolutePosition = value
        End Set
    End Property
    Public ReadOnly Property RecordCount As Integer
        Get
            If Not table Is Nothing Then
                If Not table.Rows Is Nothing Then
                    Return table.Rows.Count
                End If
            End If
            Return 0
        End Get
    End Property
    Public ReadOnly Property EOF As Boolean
        Get
            Return AbsolutePosition >= RecordCount
        End Get
    End Property
    Public ReadOnly Property BOF As Boolean
        Get
            Return AbsolutePosition = 0
        End Get
    End Property

    Public Function FieldExists(F As String) As Boolean
        If Not table Is Nothing Then
            If Not table.Columns Is Nothing Then
                Return table.Columns.Contains(F)
            End If
        End If
        Return False
    End Function


    Public Function MoveFirst() As Integer
        AbsolutePosition = 0
        Return 0
    End Function
    Public Function MoveNext() As Integer
        Return If(++AbsolutePosition < RecordCount, AbsolutePosition, AbsolutePosition = RecordCount)
    End Function
    Public Function MovePrevious() As Integer
        Return If(--AbsolutePosition >= 0, AbsolutePosition, AbsolutePosition = 0)
    End Function
    Public Function MoveLast() As Integer
        AbsolutePosition = RecordCount - 1
        Return AbsolutePosition
    End Function

    Public ReadOnly Property Fields As RecordsetFields
        Get
            If AbsolutePosition >= 0 And AbsolutePosition < RecordCount Then Return New RecordsetFields(table.Rows(AbsolutePosition))
            Throw New ArgumentOutOfRangeException("Either EOF or BOF is true.")
        End Get
    End Property

    Public ReadOnly Property FieldNames As List(Of String)
        Get
            If IsNothing(table) Then Return Nothing
            Dim result As List(Of String) = New List(Of String)
            For Each item As DataColumn In table.Columns
                result.Add(item.ColumnName)
            Next
            Return result
        End Get
    End Property

    Public ReadOnly Property Field As PropIndexer(Of Object, Object)
        Get
            Return New PropIndexer(Of Object, Object)(
                Function(k As Object)
                    Return Fields(k).Value
                End Function,
                Function(k As Object, v As Object)
                    Fields(k).Value = v
                End Function
                )
        End Get
    End Property

    Default Property Item(field As Object) As Object
        Get
            Return GetField(field)
        End Get
        Set
            SetField(field, Value)
        End Set
    End Property

    Public Function GetField(key As Object) As Object
        Return Fields(key).Value
    End Function
    Public Sub SetField(key As Object, value As Object)
        Fields(key).Value = value
    End Sub

    Public Function GetRows() As List(Of List(Of Object))
        Dim tableEnumerable As Object = table.AsEnumerable()
        Dim tableList As Object = tableEnumerable.ToArray().ToList()
        Return tableList.ToList() _
        .Select(Function(r As Object)
                    Return r.ItemArray.ToList()
                End Function) _
        .ToList()
    End Function

    Public Property Filter As String
        Get
            Return mFilter
        End Get
        Set(value As String)
            mFilter = value
            If String.IsNullOrEmpty(value) Then
                filteredTable = Nothing
                Return
            End If

            filteredTable = table.Select(mFilter).CopyToDataTable()
        End Set
    End Property

    Protected Function Find(v As String) As Boolean
        Dim temp As DataTable = table.Select(mFilter).CopyToDataTable()
        If temp.Rows.Count = 0 Then Return False
        Dim X As Integer = table.Rows.IndexOf(temp.Rows(0))
        AbsolutePosition = X
        Return True
    End Function

    Private Sub Open()
        Const maxTries = 5

        If Dir(Database) = "" Then
            MsgBox("Database Not Found: " + Database)
            Return
        End If

        Dim result As DataSet = New DataSet()
        connection = New OleDbConnection(ConnectionString(Database))
        Dim Command As OleDbCommand = New OleDbCommand(Source, connection)
        For Each Key In Parameters.Keys
            Dim param As OleDbParameter = Command.CreateParameter()
            param.ParameterName = Key
            param.Value = Parameters(Key)
        Next

        adapter = New OleDbDataAdapter(Command)
        Try
            connection.Open()
            adapter.FillSchema(result, SchemaType.Source)
            adapter.Fill(result, "Default")
        Catch e As Exception
            If Not QuietErrors Then sqlExecutionError(Source, e)
        Finally
            connection.Close()
        End Try

        table = result.Tables("Default")
    End Sub

    Public Sub Update()
        Dim cb As OleDbCommandBuilder = New OleDbCommandBuilder(adapter)
        cb.QuotePrefix = "["
        cb.QuoteSuffix = "]"
        Try
            connection.Open()
            adapter.UpdateCommand = cb.GetUpdateCommand()
            adapter.Update(table)
        Catch e As Exception
            If Not QuietErrors Then sqlExecutionError(adapter.DeleteCommand.ToString(), e)
        Finally
            connection.Close()
        End Try

        mAddingRow = False
    End Sub

    Public Sub AddNew()
        Dim newRow As DataRow = table.NewRow()
        table.Rows.InsertAt(newRow, table.Rows.Count)
        AbsolutePosition = table.Rows.Count - 1
        mAddingRow = True
    End Sub

    Public Sub Delete()
        Dim cb As OleDbCommandBuilder = New OleDbCommandBuilder(adapter)
        Try
            connection.Open()
            adapter.DeleteCommand = cb.GetDeleteCommand()
            adapter.Update(table)
        Catch e As Exception
            If Not QuietErrors Then sqlExecutionError(adapter.UpdateCommand.ToString(), e)
        Finally
            connection.Close()
        End Try
    End Sub

    Public Class RecordsetFields
        Implements ICollection
        Private row As DataRow = Nothing

        Public Sub New(row As DataRow)
            Me.row = row
        End Sub

        Public ReadOnly Property Count As Integer
            Get
                Return row.Table.Columns.Count
            End Get
        End Property
        Public SyncRoot As Object = Nothing
        Public IsSynchronized As Boolean = False

        Private Sub ICollection_CopyTo(array As Array, index As Integer) Implements ICollection.CopyTo
            Throw New InvalidOperationException("Not valid on object")
        End Sub

        Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator
            Return row.Table.Columns.GetEnumerator()
        End Function

        Default Public ReadOnly Property Item(x As Object) As RecordsetField
            Get
                Dim C As DataColumn = row.Table.Columns(x)
                Return New RecordsetField(row, x)
            End Get
        End Property

        Private ReadOnly Property ICollection_Count As Integer Implements ICollection.Count
            Get
                Throw New NotImplementedException()
            End Get
        End Property

        Private ReadOnly Property ICollection_IsSynchronized As Boolean Implements ICollection.IsSynchronized
            Get
                Throw New NotImplementedException()
            End Get
        End Property

        Private ReadOnly Property ICollection_SyncRoot As Object Implements ICollection.SyncRoot
            Get
                Throw New NotImplementedException()
            End Get
        End Property
    End Class

    Public Class RecordsetField
        Public Const adSmallInt As Integer = 2 ' Integer    SmallInt
        Public Const adInteger As Integer = 3 ' AutoNumber
        Public Const adSingle As Integer = 4 ' Single   Real
        Public Const adDouble As Integer = 5 ' Double   Float   Float
        Public Const adCurrency As Integer = 6 ' Currency   Money
        Public Const adDate As Integer = 7 ' Date   DateTime
        Public Const adIDispatch As Integer = 9 '
        Public Const adBoolean As Integer = 11 '    YesNo   Bit
        Public Const adVariant As Integer = 12 ' Sql_Variant(SQL Server 2000 +) VarChar2
        Public Const adDecimal As Integer = 14 ' Decimal *
        Public Const adUnsignedTinyInt As Integer = 17 '    Byte    TinyInt
        Public Const adBigInt As Integer = 20 ' BigInt(SQL Server 2000 +)
        Public Const adGUID As Integer = 72 ' ReplicationID(Access 97 (OLEDB)), (Access 2000 (OLEDB))   UniqueIdentifier (SQL Server 7.0 +)
        Public Const adWChar As Integer = 130 ' NChar(SQL Server 7.0 +)
        Public Const adChar As Integer = 129 ' Char Char
        Public Const adNumeric As Integer = 131 ' Decimal(Access 2000 (OLEDB))  Decimal
        Public Const adBinary As Integer = 128 ' Binary
        Public Const adDBTimeStamp As Integer = 135 ' DateTime(Access 97 (ODBC))    DateTime
        Public Const adVarChar As Integer = 200 ' Text(Access 97)   VarChar VarChar
        Public Const adLongVarChar As Integer = 201 ' Memo(Access 97)
        Public Const adVarWChar As Integer = 202 ' Text(Access 2000 (OLEDB))    NVarChar (SQL Server 7.0 +) NVarChar2
        Public Const adLongVarWChar As Integer = 203 ' Memo(Access 2000 (OLEDB))
        Public Const adVarBinary As Integer = 204 ' ReplicationID(Access 97)    VarBinary
        Public Const adLongVarBinary As Integer = 205 ' OLEObject   Image   Long Raw *

        Private Row As DataRow = Nothing
        Public Name As Object = ""
        Public Size As Integer = 0


        Public Sub New(Row As DataRow, Name As Object)
            Me.Row = Row
            Me.Name = Name
        End Sub

        Public Property Value As Object
            Get
                Return Row(Name)
            End Get
            Set(value As Object)
                Row(Name) = value
            End Set
        End Property


        Public ReadOnly Property Type As Object
            Get
                Return Row.Table.Columns(Name).DataType
            End Get
        End Property
    End Class

    Public Class PropIndexer(Of I, V)
        Public Delegate Sub setProperty(idx As I, value As V)
        Public Delegate Function getProperty(idx As I)

        Public getter As getProperty
        Public setter As setProperty

        Public Sub New(g As getProperty, s As setProperty)
            getter = g
            setter = s
        End Sub
        Public Sub New(g As getProperty)
            getter = g
            setter = AddressOf setPropertyNoop
        End Sub
        Public Sub New()
            getter = AddressOf getPropertyNoop
            setter = AddressOf setPropertyNoop
        End Sub

        Private Sub setPropertyNoop(idx As I, value As V)
            ' NOOP.  Intentionally left blank.
        End Sub
        Private Function getPropertyNoop(idx As I) As V
            Return CType(Nothing, V)
        End Function

        Default Public Property Item(ByVal nIndex As I) As V
            Get
                Return getter.Invoke(nIndex)

            End Get
            Set
                setter.Invoke(nIndex, Value)
            End Set
        End Property
    End Class

End Class

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1