Add ObjectAdapterTyped class to CSLA?

Add ObjectAdapterTyped class to CSLA?

Old forum URL: forums.lhotka.net/forums/t/4191.aspx


alancaster posted on Thursday, January 17, 2008

In writing reports that get their data from a CSLA business object, the ObjectAdapter class will take in a CSLA object and return a DataSet of all String types that you can use to bind to the report.

The problem with this is you loose a lot of functionality in the report because all the types are Strings, so if you write formulas inside the report to do number or date math, you need to do a bunch of grungy type conversions to get the report formulas to work correctly.

To get around this problem, you need a DataSet that preserves the correct field types from the original CSLA object, so I developed an "ObjectAdapterTyped" class to handle this.

Would it be possible to shared this class and possibly have it added to the CSLA distribution?

Thank You.

Alex

Skafa replied on Friday, January 18, 2008

You can always share trough the forum here (add an attachment).

Also, have a look at the CslaContrib project at CodePlex. (and the contribution forum here at lhotka.net)

alancaster replied on Wednesday, January 23, 2008

I tried to add this class file as an attachment but the Forum says I do not have permission to do so.  So here is the VB.Net code for the ObjectAdapterTyped.vb class file I have been using to create typed DataSets for report binding.  It should go in the same folder as the ObjectAdapter.vb class file.

 

'#############################################################
'## Project:    Csla
'## File:       ObjectAdapterTyped
'## Programmer: Alex Lancaster
'## Description: This started out as a copy of the CSLA
'## ObjectAdapter class, except it returns a typed ADO.Net
'## DataSet instead of a DataSet with all String type columns.
'## Not using a DataSet with properly typed columns will
'## often cause problems down the line when binding the
'## DataSet to a report writer object.
'##
'## The main difference between this class and the original
'## is the mColumns ArrayList contains DataColumns now, not
'## strings.  Explicitly typed DataColumns are created
'## in the AutoDiscover phase, and type conversions are
'## performed in the DataCopy phase.
'#############################################################

Imports System.ComponentModel
Imports System.Reflection

Namespace Data

    Public Class ObjectAdapterTyped
        Private mColumns As New ArrayList
        Private NULL As DBNull

        ''' <summary>
        ''' Fills the DataSet with data from an object or collection.
        ''' </summary>
        ''' <remarks>
        ''' The name of the DataTable being filled is will be the class name of
        ''' the object acting as the data source. The
        ''' DataTable will be inserted if it doesn't already exist in the DataSet.
        ''' </remarks>
        ''' <param name="ds">A reference to the DataSet to be filled.</param>
        ''' <param name="source">A reference to the object or collection acting as a data source.</param>
        Public Sub Fill(ByRef ds As DataSet, ByVal Source As Object)
            Dim className As String
            className = TypeName(Source)

            Fill(ds, className, Source)
        End Sub

        ''' <summary>
        ''' Fills the DataSet with data from an object or collection.
        ''' </summary>
        ''' <remarks>
        ''' The name of the DataTable being filled is specified as a parameter. The
        ''' DataTable will be inserted if it doesn't already exist in the DataSet.
        ''' </remarks>
        ''' <param name="ds">A reference to the DataSet to be filled.</param>
        ''' <param name="TableName"></param>
        ''' <param name="source">A reference to the object or collection acting as a data source.</param>
        Public Sub Fill(ByRef ds As DataSet, ByVal TableName As String, ByVal Source As Object)
            Dim dt As DataTable
            Dim exists As Boolean

            dt = ds.Tables(TableName)
            exists = Not dt Is Nothing

            If Not exists Then
                dt = New DataTable(TableName)
            End If

            Fill(dt, Source)

            If Not exists Then
                ds.Tables.Add(dt)
            End If
        End Sub

        ''' <summary>
        ''' Fills a DataTable with data values from an object or collection.
        ''' </summary>
        ''' <param name="dt">A reference to the DataTable to be filled.</param>
        ''' <param name="source">A reference to the object or collection acting as a data source.</param>
        Public Sub Fill(ByRef dt As DataTable, ByVal source As Object)
            AutoDiscover(source, True)
            DataCopy(dt, source)
        End Sub

#Region " Data Copy "

        Private Sub DataCopy(ByRef dt As DataTable, ByVal Source As Object)
            If Source Is Nothing Then Exit Sub
            If mColumns.Count < 1 Then Exit Sub

            If TypeOf Source Is IListSource Then
                DataCopyIList(dt, CType(Source, IListSource).GetList)
            ElseIf TypeOf Source Is IList Then
                DataCopyIList(dt, CType(Source, IList))
            Else
                'they gave us a regular object - create a list
                Dim col As New ArrayList
                col.Add(Source)
                DataCopyIList(dt, CType(col, IList))
            End If

        End Sub

        Private Sub DataCopyIList(ByRef dt As DataTable, ByVal ds As IList)
            Dim index As Integer
            'Dim column As String
            'Dim item As String
            Dim dc As DataColumn
            Dim dr As DataRow

            'Create columns if needed
            'For Each column In mColumns
            '    If Not dt.Columns.Contains(column) Then
            '        dt.Columns.Add(column)
            '    End If
            'Next
            For Each dc In mColumns
                If Not dt.Columns.Contains(dc.ColumnName) Then
                    dt.Columns.Add(dc)
                End If
            Next

            'load the data into the control
            dt.BeginLoadData()
            'For index = 0 To ds.Count - 1
            '    dr = dt.NewRow
            '    For Each column In mColumns
            '        Try
            '            dr(column) = GetField(ds(index), column)

            '        Catch ex As Exception
            '            dr(column) = ex.Message
            '        End Try
            '    Next
            '    dt.Rows.Add(dr)
            'Next
            For index = 0 To ds.Count - 1
                dr = dt.NewRow
                For Each dc In mColumns
                    Try
                        'Depending on DataType, convert value into correct type
                        Select Case dc.DataType.ToString
                            Case "System.String", "System.GUID"
                                'To speed up execution, I put this Case here to catch most
                                'of the string/GUID values, and the default at the bottom is
                                'still a string type
                                dr(dc.ColumnName) = ToString(GetColumn(ds(index), dc.ColumnName))
                            Case "System.Int32"
                                'item = CType(GetField(ds(index), dc.ColumnName), Int32).ToString
                                'dr(dc.ColumnName) = CType(GetField(ds(index), dc.ColumnName), Int32)
                                dr(dc.ColumnName) = ToInteger(GetColumn(ds(index), dc.ColumnName))
                            Case "System.Int16"
                                'item = CType(GetField(ds(index), dc.ColumnName), Int16).ToString
                                'dr(dc.ColumnName) = CType(GetField(ds(index), dc.ColumnName), Int16)
                                dr(dc.ColumnName) = ToShort(GetColumn(ds(index), dc.ColumnName))
                            Case "System.Int64"
                                'item = CType(GetField(ds(index), dc.ColumnName), Int64).ToString
                                'dr(dc.ColumnName) = CType(GetField(ds(index), dc.ColumnName), Int64)
                                dr(dc.ColumnName) = ToLong(GetColumn(ds(index), dc.ColumnName))
                            Case "System.Decimal"
                                'item = CType(GetField(ds(index), dc.ColumnName), Decimal).ToString
                                'dr(dc.ColumnName) = CType(GetField(ds(index), dc.ColumnName), Decimal)
                                dr(dc.ColumnName) = ToDecimal(GetColumn(ds(index), dc.ColumnName))
                            Case "System.DateTime", "Csla.SmartDate"
                                'item = CType(GetField(ds(index), dc.ColumnName), DateTime).ToString
                                dr(dc.ColumnName) = ToDate(GetColumn(ds(index), dc.ColumnName))
                            Case "System.Boolean"
                                'item = CType(GetField(ds(index), dc.ColumnName), Boolean).ToString
                                'dr(dc.ColumnName) = CType(GetField(ds(index), dc.ColumnName), Boolean)
                                dr(dc.ColumnName) = ToBoolean(GetColumn(ds(index), dc.ColumnName))
                            Case "System.Byte"
                                dr(dc.ColumnName) = ToByte(GetColumn(ds(index), dc.ColumnName))
                            Case Else
                                'String is the default type
                                'item = GetField(ds(index), dc.ColumnName).ToString
                                'dr(dc.ColumnName) = GetField(ds(index), dc.ColumnName).ToString
                                dr(dc.ColumnName) = ToString(GetColumn(ds(index), dc.ColumnName))
                        End Select

                    Catch ex As Exception
                        'dr(column) = ex.Message
                        Throw New System.Data.DataException("Error loading data: " & dc.ColumnName, ex)
                    End Try
                Next
                dt.Rows.Add(dr)
            Next
            dt.EndLoadData()

        End Sub

#End Region

#Region " AutoDiscover "

        Private Sub AutoDiscover(ByVal Source As Object, Optional ByVal AllowDBNull As Boolean = True)
            Dim innerSource As Object

            If TypeOf Source Is IListSource Then
                innerSource = CType(Source, IListSource).GetList
            Else
                innerSource = Source
            End If

            mColumns.Clear()

            If TypeOf innerSource Is DataView Then
                ScanDataView(CType(innerSource, DataView))
            ElseIf TypeOf innerSource Is IList Then
                ScanIList(CType(innerSource, IList), AllowDBNull)
            Else
                'they gave us a regular object
                ScanObject(innerSource, AllowDBNull)
            End If
        End Sub

        Private Sub ScanDataView(ByVal ds As DataView)
            Dim field As Integer

            For field = 0 To ds.Table.Columns.Count - 1
                mColumns.Add(ds.Table.Columns(field))
            Next
        End Sub

        Private Sub ScanIList(ByVal ds As IList, Optional ByVal AllowDBNull As Boolean = True)
            Dim dc As DataColumn

            If ds.Count > 0 Then
                'retrieve the first item from the list
                Dim obj As Object = ds.Item(0)

                If TypeOf obj Is ValueType AndAlso obj.GetType.IsPrimitive Then
                    'the value is a primitive value type
                    'mColumns.Add("Value")
                    dc = CreateColumnFromValueType(CType(obj, ValueType), "Value", AllowDBNull)
                    mColumns.Add(dc)

                ElseIf TypeOf obj Is String Then
                    'the value is a simple string
                    'mColumns.Add("Text")
                    dc = CreateColumnFromValueType(CType(obj, ValueType), "Text", AllowDBNull)
                    mColumns.Add(dc)

                Else
                    'we have a complex structure or object
                    ScanObject(obj, AllowDBNull)
                End If
            End If

        End Sub

        Private Sub ScanObject(ByVal Source As Object, Optional ByVal AllowDBNull As Boolean = True)
            Dim SourceType As Type = Source.GetType
            Dim column As Integer
            Dim dc As DataColumn

            'retrieve a list of all public properties
            Dim props As PropertyInfo() = SourceType.GetProperties()
            If UBound(props) >= 0 Then
                For column = 0 To UBound(props)
                    If props(column).CanRead Then
                        'Get property type
                        dc = CreateColumnFromPropertyInfo(props(column), props(column).Name, AllowDBNull)
                        'mColumns.Add(props(column).Name)
                        mColumns.Add(dc)
                    End If
                Next
            End If

            'retrieve a list of all public fields
            Dim fields As FieldInfo() = SourceType.GetFields()
            If UBound(fields) >= 0 Then
                For column = 0 To UBound(fields)
                    'Get field type
                    dc = CreateColumnFromFieldInfo(fields(column), fields(column).Name, AllowDBNull)
                    'mColumns.Add(fields(column).Name)
                    mColumns.Add(dc)
                Next
            End If
        End Sub

#End Region

#Region " Create Column "

        Private Function CreateColumnFromValueType( _
            ByVal obj As ValueType, _
            ByVal FieldName As String, _
            Optional ByVal AllowDBNull As Boolean = True) As DataColumn

            Dim dc As DataColumn

            Try
                dc = New DataColumn(FieldName)
                dc.AllowDBNull = AllowDBNull
                Select Case obj.GetType.ToString
                    Case "System.Boolean"
                        With dc
                            .DataType = System.Type.GetType("System.Boolean")
                            .DefaultValue = False
                        End With

                    Case "System.Int16"
                        With dc
                            .DataType = System.Type.GetType("System.Int16")
                            .DefaultValue = 0
                        End With

                    Case "System.Int32"
                        With dc
                            .DataType = System.Type.GetType("System.Int32")
                            .DefaultValue = 0
                        End With

                    Case "System.Int64"
                        With dc
                            .DataType = System.Type.GetType("System.Int64")
                            .DefaultValue = 0
                        End With

                    Case "System.Decimal"
                        With dc
                            .DataType = System.Type.GetType("System.Decimal")
                            .DefaultValue = 0
                        End With

                    Case "System.DateTime", "Csla.SmartDate"
                        With dc
                            .DataType = System.Type.GetType("System.DateTime")
                            '.DefaultValue = Date.MinValue
                            .DefaultValue = NULL
                        End With

                    Case Else 'Default is String which is what the original ObjectAdapter was assigning all the time
                        With dc
                            .DataType = System.Type.GetType("System.String")
                            .DefaultValue = String.Empty
                        End With
                End Select

                Return dc

            Catch ex As Exception
                Throw New System.Data.DataException("Error reading value: " & FieldName, ex)
            End Try
        End Function

        Private Function CreateColumnFromPropertyInfo( _
            ByVal obj As PropertyInfo, _
            ByVal FieldName As String, _
            Optional ByVal AllowDBNull As Boolean = True) As DataColumn

            Dim dc As DataColumn
            'Dim sourcetype As Type

            Try
                dc = New DataColumn(FieldName)
                dc.AllowDBNull = AllowDBNull
                'sourcetype = obj.GetType

                Select Case obj.PropertyType.FullName
                    Case "System.Boolean"
                        With dc
                            .DataType = System.Type.GetType("System.Boolean")
                            .DefaultValue = False
                        End With

                    Case "System.Int16"
                        With dc
                            .DataType = System.Type.GetType("System.Int16")
                            .DefaultValue = 0
                        End With

                    Case "System.Int32"
                        With dc
                            .DataType = System.Type.GetType("System.Int32")
                            .DefaultValue = 0
                        End With

                    Case "System.Int64"
                        With dc
                            .DataType = System.Type.GetType("System.Int64")
                            .DefaultValue = 0
                        End With

                    Case "System.Decimal"
                        With dc
                            .DataType = System.Type.GetType("System.Decimal")
                            .DefaultValue = 0
                        End With

                    Case "System.DateTime", "Csla.SmartDate"
                        With dc
                            .DataType = System.Type.GetType("System.DateTime")
                            '.DefaultValue = Date.MinValue
                            .DefaultValue = NULL
                        End With

                    Case Else 'Default is String which is what the original ObjectAdapter was assigning all the time
                        With dc
                            .DataType = System.Type.GetType("System.String")
                            .DefaultValue = String.Empty
                        End With
                End Select

                Return dc

            Catch ex As Exception
                Throw New System.Data.DataException("Error reading value: " & FieldName, ex)
            End Try
        End Function

        Private Function CreateColumnFromFieldInfo( _
            ByVal obj As FieldInfo, _
            ByVal FieldName As String, _
            Optional ByVal AllowDBNull As Boolean = True) As DataColumn

            Dim dc As DataColumn
            'Dim sourcetype As Type

            Try
                dc = New DataColumn(FieldName)
                dc.AllowDBNull = AllowDBNull
                'sourcetype = obj.GetType

                Select Case obj.FieldType.FullName
                    Case "System.Boolean"
                        With dc
                            .DataType = System.Type.GetType("System.Boolean")
                            .DefaultValue = False
                        End With

                    Case "System.Guid"
                        With dc
                            .DataType = System.Type.GetType("System.String")
                            .DefaultValue = String.Empty
                        End With

                    Case "System.Int16"
                        With dc
                            .DataType = System.Type.GetType("System.Int16")
                            .DefaultValue = 0
                        End With

                    Case "System.Int32"
                        With dc
                            .DataType = System.Type.GetType("System.Int32")
                            .DefaultValue = 0
                        End With

                    Case "System.Int64"
                        With dc
                            .DataType = System.Type.GetType("System.Int64")
                            .DefaultValue = 0
                        End With

                    Case "System.Decimal"
                        With dc
                            .DataType = System.Type.GetType("System.Decimal")
                            .DefaultValue = 0
                        End With

                    Case "System.DateTime", "Csla.SmartDate"
                        With dc
                            .DataType = System.Type.GetType("System.DateTime")
                            '.DefaultValue = Date.MinValue
                            .DefaultValue = NULL
                        End With

                    Case Else 'Default is String which is what the original ObjectAdapter was assigning all the time
                        With dc
                            .DataType = System.Type.GetType("System.String")
                            .DefaultValue = String.Empty
                        End With
                End Select

                Return dc

            Catch ex As Exception
                Throw New System.Data.DataException("Error reading value: " & FieldName, ex)
            End Try
        End Function

#End Region

#Region " Get Field/Column "

        'This function always returns a string, which causes some type conversion issues
        Private Function GetField(ByVal obj As Object, ByVal FieldName As String) As String
            If TypeOf obj Is DataRowView Then
                'this is a DataRowView from a DataView
                Return CType(obj, DataRowView).Item(FieldName).ToString
            ElseIf TypeOf obj Is ValueType AndAlso obj.GetType.IsPrimitive Then
                'this is a primitive value type
                Return obj.ToString
            ElseIf TypeOf obj Is String Then
                'this is a simple string
                Return CStr(obj)
            Else
                'this is a complex structure or object
                Try
                    Dim sourcetype As Type = obj.GetType

                    'see if the field is a property
                    Dim prop As PropertyInfo = sourcetype.GetProperty(FieldName)

                    If prop Is Nothing OrElse Not prop.CanRead Then
                        'no readable property of that name exists - check for a field
                        Dim field As FieldInfo = sourcetype.GetField(FieldName)

                        If field Is Nothing Then
                            'no field exists either, throw an exception
                            Throw New System.Data.DataException("No such value exists: " & FieldName)
                        Else
                            'got a field, return its value
                            If Not (field.GetValue(obj) Is Nothing) Then
                                Return field.GetValue(obj).ToString
                            Else
                                Return String.Empty
                            End If
                        End If
                    Else
                        'found a property, return its value
                        If Not (prop.GetValue(obj, Nothing)) Is Nothing Then
                            Return prop.GetValue(obj, Nothing).ToString
                        Else
                            Return String.Empty
                        End If

                    End If

                Catch ex As Exception
                    Throw New System.Data.DataException("Error reading value: " & FieldName, ex)
                End Try
            End If
        End Function

        'This function is used in combination with the Value Manipulation functions below
        Private Function GetColumn(ByVal obj As Object, ByVal FieldName As String) As Object
            If TypeOf obj Is DataRowView Then
                'this is a DataRowView from a DataView
                Return CType(obj, DataRowView).Item(FieldName)
            ElseIf TypeOf obj Is ValueType AndAlso obj.GetType.IsPrimitive Then
                'this is a primitive value type
                Return obj
            ElseIf TypeOf obj Is String Then
                'this is a simple string
                Return obj
            Else
                'this is a complex structure or object
                Try
                    Dim sourcetype As Type = obj.GetType

                    'see if the field is a property
                    Dim prop As PropertyInfo = sourcetype.GetProperty(FieldName)

                    If prop Is Nothing OrElse Not prop.CanRead Then
                        'no readable property of that name exists - check for a field
                        Dim field As FieldInfo = sourcetype.GetField(FieldName)

                        If field Is Nothing Then
                            'no field exists either, throw an exception
                            Throw New System.Data.DataException("No such value exists: " & FieldName)
                        Else
                            'got a field, return its value
                            Return field.GetValue(obj)
                        End If
                    Else
                        'found a property, return its value
                        Return prop.GetValue(obj, Nothing)
                    End If
                Catch ex As Exception
                    Throw New System.Data.DataException("Error reading value: " & FieldName, ex)
                End Try
            End If
        End Function

#End Region

#Region " Value Manipulation "

        'Various methods for manipulating values and handling DBNull values.
        Private Overloads Function ToString(ByVal Value As Object) As String
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return String.Empty
                Else
                    Return Value.ToString.Trim
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Private Overloads Function ToInteger(ByVal Value As Object) As Integer
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return 0
                Else
                    Return CInt(Value)
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Private Overloads Function ToShort(ByVal Value As Object) As Short
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return 0
                Else
                    Return CShort(Value)
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Private Overloads Function ToDecimal(ByVal Value As Object) As Decimal
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return 0
                Else
                    Return CDec(Value)
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Private Overloads Function ToLong(ByVal Value As Object) As Long
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return 0
                Else
                    Return CLng(Value)
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Private Overloads Function ToSingle(ByVal Value As Object) As Single
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return 0
                Else
                    Return CSng(Value)
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Private Overloads Function ToDouble(ByVal Value As Object) As Double
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return 0
                Else
                    Return CDbl(Value)
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Private Overloads Function ToBoolean(ByVal Value As Object) As Boolean
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return False
                End If

                If IsNumeric(Value) Then
                    If CInt(Value) = 0 Then
                        Return False
                    Else
                        Return True
                    End If
                Else
                    If Value.ToString.Trim = String.Empty Then
                        Return False
                    ElseIf UCase(Value.ToString.Trim) = "TRUE" Then
                        Return True
                    ElseIf UCase(Value.ToString.Trim) = "FALSE" Then
                        Return False
                    End If
                End If

            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Private Overloads Function ToByte(ByVal Value As Object) As Byte
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return Convert.ToByte("")
                Else
                    Return Convert.ToByte(Value)
                End If
            Catch ex As Exception
                Throw ex
            End Try
        End Function

        Private Overloads Function ToDate(ByVal Value As Object) As Date
            Try
                If IsNothing(Value) OrElse IsDBNull(Value) Then
                    Return Date.MinValue
                End If

                If Value.ToString.Trim = String.Empty Then
                    Return Date.MinValue
                ElseIf TypeOf Value Is SmartDate Then
                    Return CDate(Value.ToString)
                ElseIf IsDate(Value) Then
                    Return CDate(Value)
                Else
                    Return Date.MinValue
                End If

                'If TypeOf Value Is SmartDate Then
                '    Return CDate(Value.ToString)
                'ElseIf Value.ToString.Trim = String.Empty Then
                '    Return Date.MinValue
                'ElseIf IsDate(Value) Then
                '    Return CDate(Value)
                'Else
                '    Return Date.MinValue
                'End If

            Catch ex As Exception
                Throw ex
            End Try
        End Function

#End Region

    End Class

End Namespace

Copyright (c) Marimer LLC