Click here to Skip to main content
15,893,381 members
Articles / Programming Languages / Visual Basic

Object-Oriented database design with the DatabaseObjects library

Rate me:
Please Sign up or sign in to vote.
4.00/5 (6 votes)
31 Jan 20076 min read 110.3K   3.9K   64  
Demonstrates creating object-oriented database systems with the DatabaseObjects library.
' ___________________________________________________
'
'  � Hi-Integrity Systems 2007. All rights reserved.
'  www.hisystems.com.au - Toby Wicks
' ___________________________________________________
'

Option Strict On
Option Explicit On 

Imports DatabaseObjects
Imports System.Data
Imports System.Diagnostics


''' --------------------------------------------------------------------------------
''' <summary>
''' Represents a database connection and provides a set of functions that work
''' in conjunction with classes implementing IDatabaseObjects and IDatabaseObject. 
''' The Database class automatically generates and executes the required SQL 
''' statements to perform common database operations such as saving, deleting
''' searching etc. based on the values returned via the IDatabaseObjects and 
''' IDatabaseObject interfaces.
''' Typically, this class is only used when explicitly implementing the IDatabaseObjects 
''' and IDatabaseObject interfaces rather than inheriting from DatabaseObjects (or 
''' DatabaseObjectsEnumerable) and DatabaseObject. 
''' </summary>
''' --------------------------------------------------------------------------------
''' 
Public Class Database

    Public Enum ConnectionType
        SQLServer
        MicrosoftAccess
        MySQL
    End Enum

    Private Enum ConnectionDriver
        OleDB
        ODBC
    End Enum

    Private pobjByOrdinalLastCollection As IDatabaseObjects
    Private pobjConnection As ConnectionController
    Private pobjTransactions As TransactionsClass

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Creates a new Database instance specifying the database to connect to and SQL 
    ''' syntax to use with the database. Each function call of the Database class opens 
    ''' and closes a connection. Therefore, connection pooling should be enabled 
    ''' for optimal performance.
    ''' </summary>
    ''' 
    ''' <param name="strConnectionString">
    ''' A database connection string to either a Microsoft Access, SQLServer, or MySQL 
    ''' database. For example, 'Provider=Microsoft.Jet.OLEDB.4.0;Data 
    ''' Source=northwind.mdb;Jet OLEDB:Database Password=;'.
    ''' </param>
    ''' 
    ''' <param name="eConnectionType">
    ''' Indicates the SQL syntax to generate for the database specified in strConnectionString.
    ''' </param>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Sub New( _
        ByVal strConnectionString As String, _
        ByVal eConnectionType As ConnectionType)

        pobjConnection = New ConnectionController(strConnectionString, eConnectionType)
        pobjTransactions = New TransactionsClass(pobjConnection)

    End Sub

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Returns an instance of an object from the collection using a distinct value (see
    ''' IDatabaseObjects.DistinctFieldName). If the collection has implemented the 
    ''' IDatabaseObjects.Subset function then the objDistinctValue need only be unique 
    ''' within the collection's subset, not the entire database table.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection that contains the object.
    ''' </param>
    ''' 
    ''' <param name="objDistinctValue">
    ''' The value that uniquely identifies the object within the collection. This is the value
    ''' of the field defined by the collection's IDatabaseObjects.DistinctFieldName function.
    ''' </param>
    ''' 
    ''' <returns><see cref="IDatabaseObject" />	(DatabaseObjects.IDatabaseObject)</returns>
    ''' 
    ''' <example> Loads a product using a product ID of 123
    ''' <code>
    ''' objProduct = objDatabase.Object(NorthwindDB.Products, 123)
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function [Object]( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal objDistinctValue As Object) As IDatabaseObject

        Dim objSelect As SQL.SQLSelect = New SQL.SQLSelect
        Dim objReader As IDataReader
        Dim objSubset As SQL.SQLConditions

        With objSelect
            .Tables.Add(objCollection.TableName)
            .Tables.Joins = objCollection.TableJoins(.Tables(0), .Tables)
            .Where.Add(objCollection.DistinctFieldName, SQL.ComparisonOperator.EqualTo, objDistinctValue)
            objSubset = objCollection.Subset
            If Not objSubset Is Nothing AndAlso Not objSubset.IsEmpty Then
                .Where.Add(objSubset)
            End If
        End With

        pobjConnection.Start()
        objReader = pobjConnection.Execute(objSelect)

        If objReader.Read() Then
            [Object] = ObjectFromDataReader(objCollection, objReader)
        Else
            Throw New ObjectDoesNotExistException(objCollection, objDistinctValue)
        End If

        objReader.Close()
        pobjConnection.Finished()

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Extracts the fields to save to the database from the objItem.SaveFields function.
    ''' The fields are then written to the database using either an SQL INSERT or UPDATE 
    ''' depending on whether the object has already been saved. If the collection has 
    ''' implemented IDatabaseObjects.KeyFieldName then objItem's key is also validated to 
    ''' ensure it is not null and unique within the collection. If objCollection has 
    ''' implemented IDatabaseObjects.Subset then objItem should exist within objCollection. 
    ''' If not, a duplicate key error may occur if the obItem's key is being used in 
    ''' another subset in the same table. If a record is being amended 
    ''' (IDatabaseObject.IsSaved returns true) then the function will "AND" the collection's 
    ''' IDatabaseObjects.Subset conditions and the objItem's IDatabaseObject.DistinctValue 
    ''' value to create the WHERE clause in the UPDATE statement. Therefore, the 
    ''' combination of the IDatabaseObjects.Subset and IDatabaseObject.DistinctValue 
    ''' conditions MUST identify only one record in the table. Otherwise multiple records 
    ''' will be updated with the same data. If data is only inserted and not amended 
    ''' (usually a rare occurance) then this requirement is unnecessary.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection which contains or will contain the object to save.
    ''' </param>
    ''' 
    ''' <param name="objItem">
    ''' The object to save to the database. The values saved to the database are extracted from the 
    ''' SQLFieldValues object returned from IDatabaseObject.SaveFields.
    ''' </param>
    ''' 
    ''' <example> Saves a product object (Me) to the database.
    ''' <code>
    ''' Public Sub Save()
    ''' 
    '''     objDatabase.ObjectSave(NorthwindDB.Products, Me)
    ''' 
    ''' End Sub
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Sub ObjectSave( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal objItem As IDatabaseObject)

        Dim objFieldValues As SQL.SQLFieldValues

        Dim objUpdate As SQL.SQLUpdate
        Dim objIdentityField As SQL.SQLAutoIncrementValue
        Dim objInsert As SQL.SQLInsert
        Dim objReader As IDataReader
        Dim objSubset As SQL.SQLConditions

        'Clear the ordinal collection if an object is being saved 
        pobjByOrdinalLastCollection = Nothing

        objFieldValues = objItem.SaveFields

        If objFieldValues Is Nothing Then
            Throw New DatabaseObjectsException(Misc.TypeName(objItem) & " IDatabaseObject.SaveFields not implemented")
        End If

        'Add the distinct field value if it hasn't been added via the SaveFields sub
        If Not objFieldValues.Exists(objCollection.DistinctFieldName) Then
            If Not objCollection.DistinctFieldAutoIncrements Then
                objFieldValues.Add(objCollection.DistinctFieldName, objItem.DistinctValue)
            End If
        End If

        ItemKeyEnsureValid(objCollection, objItem, objFieldValues)

        pobjConnection.Start()

        If objItem.IsSaved Then
            objUpdate = New SQL.SQLUpdate
            objUpdate.TableName = objCollection.TableName
            objUpdate.Fields = objFieldValues
            objUpdate.Where.Add(objCollection.DistinctFieldName, SQL.ComparisonOperator.EqualTo, objItem.DistinctValue)
            objSubset = objCollection.Subset
            If Not objSubset Is Nothing AndAlso Not objSubset.IsEmpty Then
                objUpdate.Where.Add(objSubset)
            End If

            pobjConnection.ExecuteNonQuery(objUpdate)
        Else
            objInsert = New SQL.SQLInsert
            objInsert.TableName = objCollection.TableName
            objInsert.Fields = objFieldValues
            pobjConnection.ExecuteNonQuery(objInsert)

            'get the auto increment value for the newly inserted record
            If objCollection.DistinctFieldAutoIncrements Then
                objIdentityField = New SQL.SQLAutoIncrementValue
                objReader = pobjConnection.Execute(objIdentityField)
                objReader.Read()
                objItem.DistinctValue = objReader.Item(0)
                objReader.Close()
            End If

            objItem.IsSaved = True
        End If

        pobjConnection.Finished()

    End Sub

    Private Sub ItemKeyEnsureValid( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal objItem As IDatabaseObject, _
        ByVal objFieldValues As SQL.SQLFieldValues)

        Dim objReader As IDataReader
        Dim objSelect As SQL.SQLSelect
        Dim objKeyFieldValue As Object
        Dim objSubset As SQL.SQLConditions

        If objCollection.KeyFieldName <> Nothing Then
            objKeyFieldValue = ItemKeyFieldValue(objCollection, objItem, objFieldValues)
            EnsureKeyDataTypeValid(objKeyFieldValue)

            If TypeOf objKeyFieldValue Is String Then
                If DirectCast(objKeyFieldValue, String).Trim = Nothing Then
                    Throw New DatabaseObjectsException(Misc.TypeName(objItem) & " " & objCollection.KeyFieldName & " field is Null")
                End If
            End If

            objSelect = New SQL.SQLSelect

            With objSelect
                .Tables.Add(objCollection.TableName)
                .Fields.Add(objCollection.KeyFieldName)
                .Where.Add(objCollection.KeyFieldName, SQL.ComparisonOperator.EqualTo, objKeyFieldValue)
                objSubset = objCollection.Subset
                If Not objSubset Is Nothing AndAlso Not objSubset.IsEmpty Then
                    .Where.Add(objSubset)
                End If

                If objItem.IsSaved Then
                    .Where.Add(objCollection.DistinctFieldName, SQL.ComparisonOperator.NotEqualTo, objItem.DistinctValue)
                End If
            End With

            pobjConnection.Start()

            objReader = pobjConnection.Execute(objSelect)

            If objReader.Read Then
                Throw New ObjectAlreadyExistsException(objItem, objKeyFieldValue.ToString)
            End If

            objReader.Close()
            pobjConnection.Finished()
        End If

    End Sub

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Returns an object from the collection using a unique key value. 
    ''' The key must be unique within the collection. If the collection's 
    ''' IDatabaseObjects.Subset has been implemented then the key need only be unique 
    ''' within the subset specified, not the entire database table. 
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection which contains the object.
    ''' </param>
    ''' 
    ''' <param name="objKey">
    ''' The key that identifies the object with the collection. The key is the value of 
    ''' the field defined by the collection's IDatabaseObjects.KeyFieldName.
    ''' </param>
    ''' 
    ''' <returns><see cref="IDatabaseObject" />	(DatabaseObjects.IDatabaseObject)</returns>
    ''' 
    ''' <example>
    ''' <code>
    ''' Default Public ReadOnly Property Item(ByVal strProductCode As String) As Product
    '''     Get
    ''' 
    '''         Return objDatabase.ObjectByKey(Me, strProductCode)
    ''' 
    '''     End Get
    ''' End Property
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectByKey( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal objKey As Object) As IDatabaseObject

        Dim objReader As IDataReader
        Dim objSelect As SQL.SQLSelect = New SQL.SQLSelect
        Dim objSubset As SQL.SQLConditions

        EnsureKeyDataTypeValid(objKey)

        With objSelect
            .Tables.Add(objCollection.TableName)
            .Tables.Joins = objCollection.TableJoins(.Tables(0), .Tables)
            .Where.Add(objCollection.KeyFieldName, SQL.ComparisonOperator.EqualTo, objKey)
            objSubset = objCollection.Subset
            If Not objSubset Is Nothing AndAlso Not objSubset.IsEmpty Then
                .Where.Add(objSubset)
            End If
        End With

        pobjConnection.Start()

        objReader = pobjConnection.Execute(objSelect)

        If objReader.Read Then
            ObjectByKey = ObjectFromDataReader(objCollection, objReader)
        Else
            Throw New ObjectDoesNotExistException(objCollection, objKey)
        End If

        objReader.Close()
        pobjConnection.Finished()

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' ObjectByOrdinalFirst returns the first object in the collection respectively 
    ''' filtered and sorted by the collection's IDatabaseObjects.Subset and 
    ''' IDatabaseObjects.OrderBy values. It differs from ObjectByOrdinal in that it only 
    ''' loads the first record from the database table not the entire table.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection which contains the object.
    ''' </param>
    ''' 
    ''' <returns><see cref="IDatabaseObject" />	(DatabaseObjects.IDatabaseObject)</returns>
    ''' 
    ''' <example>
    ''' <code>
    ''' 'Ideal for loading default objects
    ''' Dim objDefaultSupplier As Supplier = objDatabase.ObjectByOrdinalFirst(objGlobalSuppliersInstance)
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectByOrdinalFirst( _
        ByVal objCollection As IDatabaseObjects) As IDatabaseObject

        Dim objReader As IDataReader
        Dim objSelect As SQL.SQLSelect = New SQL.SQLSelect

        With objSelect
            'only select the first row of the recordset
            .Top = 1
            .Tables.Add(objCollection.TableName)
            .Tables.Joins = objCollection.TableJoins(.Tables(0), .Tables)
            .Where = objCollection.Subset
            .OrderBy = objCollection.OrderBy
        End With

        pobjConnection.Start()

        objReader = pobjConnection.Execute(objSelect)

        If objReader.Read() Then
            ObjectByOrdinalFirst = ObjectFromDataReader(objCollection, objReader)
        Else
            Throw New ObjectDoesNotExistException(objCollection, "TOP 1")
        End If

        objReader.Close()
        pobjConnection.Finished()

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' ObjectByOrdinal returns an object from the collection at a specific ordinal
    ''' position. The ordinal position is related to the collection's 
    ''' IDatabaseObjects.Subset and IDatabaseObjects.OrderBy values. 
    ''' Typically, this function is used to enumerate through a collection of objects. 
    ''' ObjectByOrdinal will load the collection's entire recordset on the first call 
    ''' and on subsequent calls the preloaded objects are returned. 
    ''' Avoid making alternate calls to ObjectByOrdinal for different tables, as the 
    ''' entire recordset will be reloaded on each call. 
    ''' To load the first object in a table without loading the entire table, use the 
    ''' ObjectByOrdinalFirst function. 
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection that contains the object at the specified ordinal position.
    ''' </param>
    ''' 
    ''' <param name="intIndex">
    ''' The ordinal position of the object. The ordinal position depends on the collection's
    ''' IDatabaseObjects.Subset and IDatabaseObjects.OrderBy values
    ''' </param>
    ''' 
    ''' <returns><see cref="IDatabaseObject" />	(DatabaseObjects.IDatabaseObject)</returns>
    ''' 
    ''' <example> 
    ''' <code>
    ''' Default Public ReadOnly Property Item(ByVal intIndex As Integer) As Product
    '''     Get
    ''' 
    '''         Return objDatabase.ObjectByOrdinal(Me, intIndex)
    ''' 
    '''     End Get
    ''' End Property
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectByOrdinal( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal intIndex As Integer) As IDatabaseObject

        Static objObjects As IList

        If Not pobjByOrdinalLastCollection Is objCollection Then
            objObjects = ObjectsList(objCollection)
            pobjByOrdinalLastCollection = objCollection
        End If

        Return DirectCast(objObjects(intIndex), IDatabaseObject)

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Returns the number of items in the collection. If the collection's 
    ''' IDatabaseObjects.Subset has been implemented then this function returns the 
    ''' number of records within the subset, not the entire table.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The object for which the number of records are returned.
    ''' </param>
    ''' 
    ''' <returns><see cref="Int32" />	(System.Int32)</returns>
    ''' 
    ''' <example> 
    ''' <code>
    ''' 'Return the number of items in this collection.
    ''' Public ReadOnly Property Count() As Integer
    '''     Get
    ''' 
    '''         Return objDatabase.ObjectsCount(Me)
    ''' 
    '''     End Get
    ''' End Property
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectsCount( _
        ByVal objCollection As IDatabaseObjects) As Integer

        Dim objReader As IDataReader
        Dim objSelect As SQL.SQLSelect = New SQL.SQLSelect
        Dim objSubset As SQL.SQLConditions

        With objSelect
            .Tables.Add(objCollection.TableName)
            .Where = objCollection.Subset
            .Fields.Add("", SQL.AggregateFunction.Count)
        End With

        pobjConnection.Start()

        objReader = pobjConnection.Execute(objSelect)
        objReader.Read()

        ObjectsCount = CType(objReader.Item(0), Integer)

        objReader.Close()
        pobjConnection.Finished()

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Returns whether the key exists within the collection. If the collection's 
    ''' IDatabaseObjects.Subset has been set then only the subset is searched not the 
    ''' entire table.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection to search within. 
    ''' </param>
    ''' 
    ''' <param name="objKey">
    ''' The key value to search by.
    ''' </param>
    ''' 
    ''' <returns><see cref="Boolean" />	(System.Boolean)</returns>
    ''' 
    ''' <example> 
    ''' <code>
    ''' Public Function Exists(ByVal strProductCode As String) As Boolean
    ''' 
    '''     Return objDatabase.ObjectExists(Me, strProductCode)
    ''' 
    ''' End Function
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectExists( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal objKey As Object) As Boolean

        Dim objReader As IDataReader
        Dim objSelect As SQL.SQLSelect = New SQL.SQLSelect
        Dim objSubset As SQL.SQLConditions

        EnsureKeyDataTypeValid(objKey)

        With objSelect
            .Tables.Add(objCollection.TableName)
            '.Fields.Add objCollection.DistinctFieldName
            .Where.Add(objCollection.KeyFieldName, SQL.ComparisonOperator.EqualTo, objKey)
            objSubset = objCollection.Subset
            If Not objSubset Is Nothing AndAlso Not objSubset.IsEmpty Then
                .Where.Add(objSubset)
            End If
        End With

        pobjConnection.Start()

        objReader = pobjConnection.Execute(objSelect)
        ObjectExists = objReader.Read

        objReader.Close()
        pobjConnection.Finished()

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Deletes an object's database record. If the collection's IDatabaseObjects.Subset 
    ''' has been implemented then the object must exist within the subset, otherwise the 
    ''' object will not be deleted. If the object has not been saved to the database the 
    ''' function will exit without executing an SQL DELETE command. After deleting the 
    ''' database record the object is set to Nothing. The calling function should receive 
    ''' the object ByRef for this to have any affect. Setting the object to Nothing  
    ''' minimises the possibility of the deleted object being used in code after 
    ''' ObjectDelete has been called.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection that contains the object to delete. If the item does not exist 
    ''' within the collection then the object will not be deleted.
    ''' </param>
    ''' 
    ''' <param name="objItem">
    ''' The object to delete. The calling function should receive this object ByRef 
    ''' as the object is set to Nothing after deletion. 
    ''' Reference Type: <see cref="IDatabaseObject" />	(DatabaseObjects.IDatabaseObject)
    ''' </param>
    ''' 
    ''' <example> 
    ''' <code>
    ''' Public Sub Delete(ByRef objProduct As Product)
    ''' 
    '''     objDatabase.ObjectDelete(Me, objProduct)
    '''     'objProduct will now be Nothing
    ''' 
    ''' End Sub 
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Sub ObjectDelete( _
        ByVal objCollection As IDatabaseObjects, _
        ByRef objItem As IDatabaseObject)

        If objItem.IsSaved Then
            Dim objDelete As SQL.SQLDelete = New SQL.SQLDelete
            Dim objSubset As SQL.SQLConditions

            'Clear the ordinal collection if an object is being deleted 
            pobjByOrdinalLastCollection = Nothing

            With objDelete
                .TableName = objCollection.TableName
                .Where.Add(objCollection.DistinctFieldName, SQL.ComparisonOperator.EqualTo, objItem.DistinctValue)
                objSubset = objCollection.Subset
                If Not objSubset Is Nothing AndAlso Not objSubset.IsEmpty Then
                    .Where.Add(objSubset)
                End If
            End With

            pobjConnection.Start()
            pobjConnection.ExecuteNonQuery(objDelete)
            pobjConnection.Finished()

            objItem.IsSaved = False
        End If

        'The function that calls ObjectDelete objItem MUST be ByRef for this to have any effect
        objItem = Nothing

    End Sub

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Deletes all of the objects in the collection. If IDatabaseObjects.Subset 
    ''' has been implemented then only the objects within the subset are deleted, not 
    ''' the table's entire contents.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection from which all objects are to be deleted.
    ''' </param>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Sub ObjectsDeleteAll( _
        ByVal objCollection As IDatabaseObjects)

        Dim objDelete As SQL.SQLDelete = New SQL.SQLDelete

        'Clear the ordinal collection if an object is being deleted 
        pobjByOrdinalLastCollection = Nothing

        With objDelete
            .TableName = objCollection.TableName
            .Where = objCollection.Subset
        End With

        pobjConnection.Start()
        pobjConnection.ExecuteNonQuery(objDelete)
        pobjConnection.Finished()

    End Sub

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Returns an IList object containing all of the collection's associated child 
    ''' objects. This function is useful when loading a set of objects for a subset or 
    ''' for use with the IEnumerable interface. 
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection which contains the objects to load.
    ''' </param>
    ''' 
    ''' <returns><see cref="Collections.IList" />	(System.Collections.IList)</returns>
    ''' 
    ''' <example> 
    ''' <code>
    ''' 'Can be used to provide an enumerator for use with the "For Each" clause
    ''' Private Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
    ''' 
    '''     Return objDatabase.ObjectsList(objGlobalProductsInstance).GetEnumerator
    ''' 
    ''' End Function
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectsList( _
        ByVal objCollection As IDatabaseObjects) As IList

        Dim objArrayList As IList = New ArrayList
        Dim objSelect As SQL.SQLSelect = New SQL.SQLSelect
        Dim objReader As IDataReader

        With objSelect
            .Tables.Add(objCollection.TableName)
            .Tables.Joins = objCollection.TableJoins(.Tables(0), .Tables)
            .Where = objCollection.Subset
            .OrderBy = objCollection.OrderBy
        End With

        pobjConnection.Start()
        objReader = pobjConnection.Execute(objSelect)

        While objReader.Read
            objArrayList.Add(ObjectFromDataReader(objCollection, objReader))
        End While

        objReader.Close()
        pobjConnection.Finished()

        Return objArrayList

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Returns an IDictionary object. Each key/value pair contains a key and
    ''' the object associated with the key.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection which specifies the objects to load.
    ''' </param>
    ''' 
    ''' <returns><see cref="Collections.IDictionary" />	(System.Collections.IDictionary)</returns>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectsDictionary( _
        ByVal objCollection As IDatabaseObjects) As IDictionary

        Return ObjectsDictionaryBase(objCollection)

    End Function


    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Returns an IDictionary object. Each key/value pair contains a distinct 
    ''' value and the object associated with the distinct value.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection which specifies the objects to load.
    ''' </param>
    ''' 
    ''' <returns><see cref="Collections.IDictionary" />	(System.Collections.IDictionary)</returns>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectsDictionaryByDistinctValue( _
        ByVal objCollection As IDatabaseObjects) As IDictionary

        Return ObjectsDictionaryBase(objCollection, bKeyIsDistinctField:=True)

    End Function

    Private Function ObjectsDictionaryBase( _
        ByVal objCollection As IDatabaseObjects, _
        Optional ByVal bKeyIsDistinctField As Boolean = False) As IDictionary

        'Returns an IDictionary with the key being either the DistinctField or KeyField

        Dim objDictionary As IDictionary = New Hashtable
        Dim objSelect As SQL.SQLSelect = New SQL.SQLSelect
        Dim objReader As IDataReader
        Dim strKeyField As String

        With objSelect
            .Tables.Add(objCollection.TableName)
            .Tables.Joins = objCollection.TableJoins(.Tables(0), .Tables)
            .Where = objCollection.Subset
            .OrderBy = objCollection.OrderBy
        End With

        pobjConnection.Start()
        objReader = pobjConnection.Execute(objSelect)

        If bKeyIsDistinctField Then
            strKeyField = objCollection.DistinctFieldName
        Else
            strKeyField = objCollection.KeyFieldName
        End If

        While objReader.Read
            objDictionary.Add(objReader(strKeyField), ObjectFromDataReader(objCollection, objReader))
        End While

        objReader.Close()
        pobjConnection.Finished()

        Return objDictionary

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Returns a collection of objects that match the specified search criteria. 
    ''' This function utilises any subsets, ordering or table joins specified in the 
    ''' collection. To add a set of conditions to the objSearchCriteria object with 
    ''' higher precendance use the "Add(SQLConditions)" overloaded function as this will 
    ''' wrap the conditions within parentheses.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection to search within.
    ''' </param>
    ''' 
    ''' <param name="objSearchCriteria">
    ''' The criteria to search for within the collection. To add a set of conditions with 
    ''' with higher precendance use the "Add(SQLConditions)" overloaded function as this 
    ''' will wrap the conditions within parentheses.
    ''' </param>
    ''' 
    ''' <returns><see cref="Collections.IList" />	(System.Collections.IList)</returns>
    ''' 
    ''' <remarks>
    ''' The following wildcard characters are used when using the LIKE operator (extract
    ''' from Microsoft Transact-SQL Reference)
    ''' 
    ''' 
    ''' <font size="1">
    ''' <table width="659" border="1" cellspacing="1" cellpadding="4">
    '''   <tr>
    '''     <th width="16%" height="20">Wildcard character</th>
    '''     <th width="22%">Description</th>
    '''     <th width="62%">Example</th>
    '''   </tr>
    '''   <tr>
    '''     <td>%</td>
    '''     <td>Any string of zero or more characters.</td>
    '''     <td>WHERE title LIKE '%computer%' finds all book titles with the word 
    '''         'computer' anywhere in the book title.</td>
    '''   </tr>
    '''   <tr>
    '''     <td>_ (underscore)</td>
    '''     <td>Any single character.</td>
    '''     <td>WHERE au_fname LIKE '_ean' finds all four-letter first names that end
    '''       with ean (Dean, Sean, and so on).</td>
    '''   </tr>
    ''' </table>
    ''' </font>
    ''' </remarks>
    ''' 
    ''' <example> 
    ''' <code>
    ''' Public Function Search(ByVal objSearchCriteria As Object, ByVal eType As SearchType) As IList
    ''' 
    '''     Dim objConditions As SQL.SQLConditions = New SQL.SQLConditions
    ''' 
    '''     Select Case eType
    '''         Case SearchType.DescriptionPrefix
    '''             objConditions.Add("ProductName", SQL.ComparisonOperator.Like, objSearchCriteria &amp; "%")
    '''         Case SearchType.Description
    '''             objConditions.Add("ProductName", SQL.ComparisonOperator.Like, "%" &amp; objSearchCriteria &amp; "%")
    '''     End Select
    '''
    '''     Return objDatabase.ObjectsSearch(objGlobalProductsInstance, objConditions)
    '''
    ''' End Function
    ''' </code>
    ''' </example>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectsSearch( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal objSearchCriteria As SQL.SQLConditions) As IList

        Dim objReader As IDataReader
        Dim objSelect As SQL.SQLSelect = New SQL.SQLSelect
        Dim objResults As ArrayList = New ArrayList

        If objSearchCriteria.IsEmpty Then
            Throw New ArgumentException("Search criteria is empty")
        End If

        With objSelect
            .Tables.Add(objCollection.TableName)
            .Tables.Joins = objCollection.TableJoins(.Tables(0), .Tables)
            .OrderBy = objCollection.OrderBy
            .Where = objCollection.Subset

            If Not objSearchCriteria Is Nothing Then
                If .Where Is Nothing Then .Where = New SQL.SQLConditions
                .Where.Add(objSearchCriteria)
            End If
        End With

        pobjConnection.Start()
        objReader = pobjConnection.Execute(objSelect)

        While objReader.Read
            objResults.Add(ObjectFromDataReader(objCollection, objReader))
        End While

        objReader.Close()
        pobjConnection.Finished()

        Return objResults

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Loads an object from the current record of an IDataReader object.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection associated with the IDataReader object. 
    ''' </param>
    ''' 
    ''' <param name="objReader">
    ''' The data to be copied into a new IDatabaseObject object.
    ''' </param>
    ''' 
    ''' <returns><see cref="IDatabaseObject" />	(DatabaseObjects.IDatabaseObject)</returns>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectFromDataReader( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal objReader As IDataReader) As IDatabaseObject

        Dim strFieldName As String
        Dim strTablePrefix As String
        Dim objFieldValues As SQL.SQLFieldValues

        'check that the distinct field name exists
        If Not FieldExists(objReader, objCollection.DistinctFieldName) Then
            Throw New DatabaseObjectsException("DistinctFieldName '" & objCollection.DistinctFieldName & "' is invalid.")
        End If

        objFieldValues = New SQL.SQLFieldValues
        strTablePrefix = objCollection.TableName & "."

        'Copy the recordset values into the SQL.SQLFieldValues object
        For intIndex As Integer = 0 To objReader.FieldCount - 1
            'If the recordset has been loaded with a join then it may be prefixed with
            'the table name - this is the case with Microsoft Access
            'If so remove the table name if the table prefix is the same as objCollection.TableName
            'All of the other joined fields with tablename prefixes on the fields will remain. This is ok considering
            'most of the time an inner join has been performed where the field names are equal in the 2 joined tables
            strFieldName = objReader.GetName(intIndex)
            If strFieldName.IndexOf(strTablePrefix) = 0 Then
                objFieldValues.Add(strFieldName.Substring(strTablePrefix.Length), objReader(intIndex))
            Else
                objFieldValues.Add(strFieldName, objReader(intIndex))
            End If
        Next

        Return ObjectFromFieldValues(objCollection, objFieldValues)

    End Function

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Loads an object from the values contained in an SQLFieldValues object. 
    ''' This function is generally used from within an IDatabaseObject.Load function when 
    ''' the IDatabaseObjects.TableJoins function has been implemented.
    ''' </summary>
    ''' 
    ''' <param name="objCollection">
    ''' The collection associated with the field values.
    ''' </param>
    ''' 
    ''' <param name="objFieldValues">
    ''' The data container from which to load a new object.
    ''' </param>
    ''' 
    ''' <returns><see cref="IDatabaseObject" />	(DatabaseObjects.IDatabaseObject)</returns>
    ''' --------------------------------------------------------------------------------
    ''' 
    Public Function ObjectFromFieldValues( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal objFieldValues As SQL.SQLFieldValues) As IDatabaseObject

        Dim objItem As IDatabaseObject = objCollection.ItemInstance

        objItem.IsSaved = True
        objItem.DistinctValue = objFieldValues(objCollection.DistinctFieldName).Value
        objItem.LoadFields(objFieldValues)

        Return objItem

    End Function

    Private Function FieldExists( _
        ByVal objReader As IDataReader, _
        ByVal strFieldName As String) As Boolean

        Dim bExists As Boolean
        Dim strReaderFieldName As String

        For intIndex As Integer = 0 To objReader.FieldCount - 1
            strReaderFieldName = objReader.GetName(intIndex)
            If strReaderFieldName.IndexOf("."c) >= 0 Then
                strReaderFieldName = strReaderFieldName.Split("."c)(1)
            End If
            If String.Compare(strReaderFieldName, strFieldName, ignoreCase:=True) = 0 Then
                bExists = True
                Exit For
            End If
        Next

        Return bExists

    End Function

    Private Sub EnsureKeyDataTypeValid(ByVal objKey As Object)

        'If TypeOf objKey Is Object Then
        '    Throw New ArgumentInvalidDataTypeException(objKey)
        'End If

    End Sub

    Private Function ItemKeyFieldValue( _
        ByVal objCollection As IDatabaseObjects, _
        ByVal objItem As IDatabaseObject, _
        ByVal objFieldValues As SQL.SQLFieldValues) As Object

        'On the rare occurance that the KeyField is the same as the DistinctField
        'then the key value may not have been set in the Save and therefore be
        'available in the objFieldValues collection. In which case the
        'key has to be extracted from the objItem.DistinctField.
        Dim objKeyFieldValue As Object

        If String.Compare(objCollection.DistinctFieldName, objCollection.KeyFieldName, ignoreCase:=True) = 0 Then
            objKeyFieldValue = objItem.DistinctValue
        Else
            objKeyFieldValue = objFieldValues(objCollection.KeyFieldName).Value
        End If

        Return objKeyFieldValue

    End Function

    Public ReadOnly Property Transactions() As TransactionsClass
        Get

            Return pobjTransactions

        End Get
    End Property

    Friend ReadOnly Property Connection() As ConnectionController
        Get

            Return pobjConnection

        End Get
    End Property

    ''' --------------------------------------------------------------------------------
    ''' <summary>
    ''' Provides a mechanism for starting beginning, commiting and rolling back transactions.
    ''' </summary>
    ''' --------------------------------------------------------------------------------
    Public Class TransactionsClass

        Private pobjConnection As IDbConnection
        Private pobjConnectionController As ConnectionController

        Friend Sub New(ByVal objConnection As ConnectionController)

            pobjConnectionController = objConnection

        End Sub

        ''' --------------------------------------------------------------------------------
        ''' <summary>
        ''' Notifies that a transaction has begun and that all modifications to the database
        ''' are only committed after a call to Commit. Alternatively, if 
        ''' Rollback is called then all changes are aborted. To execute other 
        ''' statements for the transaction call the Execute and ExecuteNonQuery functions. 
        ''' Because all changes to the database must be executed on the same connection 
        ''' DatabaseObjects maintains an open connection until the Commit or Rollback functions 
        ''' are called. When transactions are not being used connections are opened and closed 
        ''' for each SQL statement executed i.e. (INSERT/UPDATE/SELECT...).
        ''' </summary>
        '''
        ''' <example> 
        ''' <code>
        ''' Public Sub Shadows Save()
        ''' 
        '''     Mybase.ParentDatabase.Transactions.Begin()
        ''' 
        '''     MyBase.Save
        '''     Me.Details.Save
        '''
        '''     'Execute any other statements here via
        '''     'MyBase.ParentDatabase.Transactions.Execute()...
        '''
        '''     Mybase.ParentDatabase.Transactions.Commit()
        '''
        ''' End sub
        ''' </code>
        ''' </example>
        ''' --------------------------------------------------------------------------------
        Public Sub Begin()

            pobjConnection = pobjConnectionController.BeginTransaction()

        End Sub

        ''' --------------------------------------------------------------------------------
        ''' <summary>
        ''' Commits all statements that have been executed after the Begin() call.
        ''' The database connection is closed after the transaction has been committed.
        ''' </summary>
        '''
        ''' <example> 
        ''' <code>
        ''' Public Sub Shadows Save()
        ''' 
        '''     Mybase.ParentDatabase.Transactions.Begin()
        ''' 
        '''     MyBase.Save
        '''     Me.Details.Save
        '''
        '''     'Execute any other statements here via
        '''     'MyBase.ParentDatabase.Transactions.Execute()...
        '''
        '''     Mybase.ParentDatabase.Transactions.Commit()
        '''
        ''' End sub
        ''' </code>
        ''' </example>
        ''' --------------------------------------------------------------------------------
        Public Sub Commit()

            pobjConnectionController.CommitTransaction()

        End Sub

        ''' --------------------------------------------------------------------------------
        ''' <summary>
        ''' Rollsback all statements that have been executed after the Begin() call.
        ''' The database connection is closed after the transaction has been rolled back.
        ''' </summary>
        ''' --------------------------------------------------------------------------------
        '''
        Public Sub Rollback()

            pobjConnectionController.RollbackTransaction()

        End Sub

        ''' --------------------------------------------------------------------------------
        ''' <summary>
        ''' Allows an SQL statement to be executed on the current transaction connection.
        ''' If a transaction is not in progress and exception will occur.
        ''' </summary>
        ''' --------------------------------------------------------------------------------
        Public Sub ExecuteNonQuery(ByVal objStatement As SQL.ISQLStatement)

            pobjConnectionController.ExecuteNonQuery(objStatement)

        End Sub

        ''' --------------------------------------------------------------------------------
        ''' <summary>
        ''' Allows an SQL statement to be executed on the current transaction connection.
        ''' If a transaction is not in progress and exception will occur.
        ''' </summary>
        ''' --------------------------------------------------------------------------------
        Public Function Execute(ByVal objStatement As SQL.ISQLStatement) As IDataReader

            Return pobjConnectionController.Execute(objStatement)

        End Function

    End Class

    Friend Class ConnectionController

        Private pobjConnection As IDbConnection
        Private pbInTransactionMode As Boolean = False

        Private pstrConnectionString As String
        Private peConnectionType As ConnectionType
        Private peConnectionDriver As ConnectionDriver

        Friend Sub New( _
            ByVal strConnectionString As String, _
            ByVal eConnectionType As ConnectionType)

            pstrConnectionString = strConnectionString
            peConnectionType = eConnectionType
            SQL.SQLStatement.DefaultConnectionType = eConnectionType
            peConnectionDriver = ExtractConnectionDriver(strConnectionString)

        End Sub

        Friend Sub Start()

            'If not in transaction mode then open a new connection
            'If in transaction mode a connection should already be open
            If Not Me.InTransactionMode Then
                pobjConnection = CreateConnection()
                pobjConnection.Open()
            End If

        End Sub

        Friend Sub Finished()

            'If not in transaction mode then close the connection
            'If in transaction mode a connection should be left open
            If Not Me.InTransactionMode Then
                pobjConnection.Close()
            End If

        End Sub

        Friend Function BeginTransaction() As IDbConnection

            pbInTransactionMode = True
            pobjConnection = CreateConnection()
            pobjConnection.Open()
            ExecuteNonQuery(New SQL.SQLBeginTransaction)

            Return pobjConnection

        End Function

        Friend Sub CommitTransaction()

            ExecuteNonQuery(New SQL.SQLCommitTransaction)
            pobjConnection.Close()
            pbInTransactionMode = False

        End Sub

        Friend Sub RollbackTransaction()

            ExecuteNonQuery(New SQL.SQLRollbackTransaction)
            pobjConnection.Close()
            pbInTransactionMode = False

        End Sub

        Private ReadOnly Property InTransactionMode() As Boolean
            Get

                Return pbInTransactionMode

            End Get
        End Property

        Friend Function Execute( _
            ByVal objSQLStatement As SQL.ISQLStatement) As IDataReader

            Return ExecuteInternal(pobjConnection, objSQLStatement)

        End Function

        Friend Sub ExecuteNonQuery( _
            ByVal objSQLStatement As SQL.ISQLStatement)

            ExecuteNonQueryInternal(pobjConnection, objSQLStatement)

        End Sub

        Private Function ExecuteInternal( _
            ByVal objConnection As IDbConnection, _
            ByVal objSQLStatement As SQL.ISQLStatement) As IDataReader

            objSQLStatement.ConnectionType = peConnectionType
            Dim strSQL As String = objSQLStatement.SQL

            DebugSQL(strSQL)
            With objConnection.CreateCommand
                .CommandText = strSQL
                Try
                    Return .ExecuteReader()
                Catch ex As Exception
                    Throw New DatabaseObjectsException("Execute failed: " & strSQL)
                End Try
            End With

        End Function

        Private Sub ExecuteNonQueryInternal( _
            ByVal objConnection As IDbConnection, _
            ByVal objSQLStatement As SQL.ISQLStatement)

            objSQLStatement.ConnectionType = peConnectionType
            Dim strSQL As String = objSQLStatement.SQL

            DebugSQL(strSQL)
            With objConnection.CreateCommand
                .CommandText = strSQL
                Try
                    .ExecuteNonQuery()
                Catch ex As Exception
                    Throw New DatabaseObjectsException("ExecuteNonQuery failed: " & strSQL)
                End Try
            End With

        End Sub

        Private Function CreateConnection() As IDbConnection

            Select Case peConnectionDriver
                Case ConnectionDriver.OleDB
                    Return New OleDb.OleDbConnection(pstrConnectionString)
                Case ConnectionDriver.ODBC
                    Return New Odbc.OdbcConnection(pstrConnectionString)
            End Select

        End Function

        Private Function ExtractConnectionDriver( _
            ByVal strConnectionString As String) As ConnectionDriver

            'This function searches for an occurance of 'Provider='
            'If found then it is assumed to be an OLEDB connection otherwise an ODBC connection

            Dim strProperties() As String = strConnectionString.Split(";"c)
            Dim strPropertyValueArray() As String
            Dim eConnectionDriver As ConnectionDriver = ConnectionDriver.ODBC

            For Each strPropertyValue As String In strProperties
                strPropertyValueArray = strPropertyValue.Split("="c)
                If String.Compare(strPropertyValueArray(0).Trim, "Provider", ignoreCase:=True) = 0 Then
                    eConnectionDriver = ConnectionDriver.OleDB
                    Exit For
                End If
            Next

            Return eConnectionDriver

        End Function

        Private Sub DebugSQL(ByVal strSQL As String)

#If DebugSQL Then
            System.Diagnostics.Debug.WriteLine(strSQL)
#End If

        End Sub

    End Class

End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions