Search code examples
asp.netdatatablestrongly-typed-dataset

Untyped to strongly typed datatable


I tried to make some experiment today. I have an application that uses untyped datatables as the model entities. They are all made like:

Imports System.Data
Imports System.Runtime.Serialization
Imports System.ComponentModel

<DesignerCategory("Code"), system.Serializable()>
Partial Public Class SomeTable1
    Inherits DataTable

#Region
    Public Const TABLE_NAME As String = "SomeTable1"

    Public Const FIELD_SomeField1 As String = "SomeField1"
    Public Const FIELD_SomeField2 As String = "SomeField2"
#End Region

    Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        MyBase.New(info, context)
    End Sub

    Public Sub New()
        MyBase.New()

        With Columns
            .Add(FIELD_SomeField1, GetType(System.String)).DefaultValue = String.Empty
            .Add(FIELD_SomeField2, GetType(System.Double)).DefaultValue = 0
        End With

        Dim keys(1) As DataColumn
        keys(0) = Columns(FIELD_SomeField1)
        TableName = TABLE_NAME
        PrimaryKey = keys
    End Sub
End Class

I'm currently working with EF, so in my razzle, I wrote something like this (yeah, it's vb):

Partial Public Class SomeTable1
    Inherits DataTable

    <Key()>
    Friend Property SomePK1 As DataColumn

    <Required(ErrorMessage:="SomeField1 is required.")>
    <DataType(DataType.Text)>
    Friend Property SomeField1 As DataColumn

    <Required()>
    <DataType(DataType.DateTime)>
    Friend Property SomeField2 As DataColumn
    ...

    Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)
        MyBase.New(info, context)
    End Sub

    Public Sub New()
        MyBase.New()

        SomeField2 = Date.Now
    End Sub
End Class

I was dreaming on making something equivalent to the former dt and being completely compatible with the current data engine.

And then the type conversion error (system date to datacolumn) broke my hopes. I must admit that has been a hard weekend :)

So before I completely discard the change, Is there any way of writing a Typed datatable so it's equivalent to the code above but with some new goodies? That's so ancient way of programming I can't find anything on the net. Thanks in advance.


Solution

  • I found how to do what I wanted. Perhaps involves some work, but it works. Knowing that this is such an obsolete way of doing things, I'm posting there so others like me that are forced to maintain old programs can benefit.

    The template for doing a typed datatable is the following:

    Imports System.Data
    Imports System.ComponentModel
    Imports System.Runtime.Serialization
    Imports System.Diagnostics
    
    '''<summary>
    '''Represents the strongly named DataTable class.
    '''</summary>
    <Global.System.Serializable(), _
     Global.System.Xml.Serialization.XmlSchemaProviderAttribute("GetTypedTableSchema")> _
    Partial Public Class tblMyTable
        Inherits TypedTableBase(Of tblMyTableRow)
    
        'Those are the StoredProcs names for (MANUAL) CRUD operations that the DBContext wrapper uses. (yuck! I hate thousands of them)
        'Public Const COMMAND_SAVE As String = "sp_MyTable_Save"
        'Public Const COMMAND_DELETE As String = "sp_MyTable_Delete"
        'Public Const COMMAND_LOADBY_ID As String = "sp_MyTable_LoadBy_Id"
    
        'Those are constants I maintain for untyped (but somewhat strong) compatibility
        Public Const FIELD_pID As String = "pID"
        Public Const FIELD_SomeOther As String = "SomeOtherField"
    
        'Basic CRUD, uses company data as the app hot swapps DBs (one for company)
        'Public Function Save(ByVal company As DataRow) As Short
        '    Return New Base(company).Update(Me, COMMAND_SAVE, COMMAND_DELETE)
        'End Function
    
        'Public Sub LoadByID(ByVal company As DataRow, Id As Integer)
        '    Me.Rows.Clear()
        '    Me.Merge(New Base(company).FillDataTable(Of tblMyTable)(COMMAND_LOADBY_ID, Id))
        'End Sub
    
        <DebuggerNonUserCodeAttribute()>
        Private Sub InitClass()
            Me.columnpID = New DataColumn(FIELD_pID, GetType(Integer), Nothing, MappingType.Element) With
                       {.AllowDBNull = False, .ReadOnly = True, .Unique = True,
                        .AutoIncrement = True, .AutoIncrementSeed = -1, .AutoIncrementStep = -1}
            MyBase.Columns.Add(Me.columnpID)
            Me.columnSomeOtherField = New DataColumn(FIELD_SomeOther, GetType(String), Nothing, MappingType.Element) With
                               {.MaxLength = 5, .AllowDBNull = False, .DefaultValue = String.Empty}
            MyBase.Columns.Add(Me.columnSomeOtherField)
        End Sub
    
        Private columnpID As DataColumn
        Private columnSomeOtherField As DataColumn
    
        <DebuggerNonUserCodeAttribute()>
        Public Sub New()
            MyBase.New()
            Me.TableName = "tblMyTable"
            Me.BeginInit()
            Me.InitClass()
            Me.EndInit()
        End Sub
    
        <DebuggerNonUserCodeAttribute()>
        Friend Sub New(ByVal table As DataTable)
            MyBase.New()
            Me.TableName = table.TableName
            If (table.CaseSensitive <> table.DataSet.CaseSensitive) Then
                Me.CaseSensitive = table.CaseSensitive
            End If
            If (table.Locale.ToString <> table.DataSet.Locale.ToString) Then
                Me.Locale = table.Locale
            End If
            If (table.Namespace <> table.DataSet.Namespace) Then
                Me.Namespace = table.Namespace
            End If
            Me.Prefix = table.Prefix
            Me.MinimumCapacity = table.MinimumCapacity
        End Sub
    
        <DebuggerNonUserCodeAttribute()>
        Protected Sub New(ByVal info As Global.System.Runtime.Serialization.SerializationInfo, ByVal context As Global.System.Runtime.Serialization.StreamingContext)
            MyBase.New(info, context)
            Me.InitVars()
        End Sub
    
        <DebuggerNonUserCodeAttribute()>
        Public ReadOnly Property pIDColumn() As DataColumn
            Get
                Return Me.columnpID
            End Get
        End Property
    
        <DebuggerNonUserCodeAttribute()>
        Public ReadOnly Property SomeOtherFieldColumn() As DataColumn
            Get
                Return Me.columnSomeOtherField
            End Get
        End Property
    
        <DebuggerNonUserCodeAttribute(), Browsable(False)>
        Public ReadOnly Property Count() As Integer
            Get
                Return Me.Rows.Count
            End Get
        End Property
    
        <DebuggerNonUserCodeAttribute()>
        Default Public ReadOnly Property Item(ByVal index As Integer) As tblMyTableRow
            Get
                Return CType(Me.Rows(index), tblMyTableRow)
            End Get
        End Property
    
        <DebuggerNonUserCodeAttribute()>
        Public Overrides Function Clone() As DataTable
            Dim cln As tblMyTable = CType(MyBase.Clone, tblMyTable)
            cln.InitVars()
            Return cln
        End Function
    
        <DebuggerNonUserCodeAttribute()>
        Protected Overrides Function CreateInstance() As DataTable
            Return New tblMyTable()
        End Function
    
        <DebuggerNonUserCodeAttribute()>
        Friend Sub InitVars()
            Me.columnpID = MyBase.Columns(FIELD_pID)
            Me.columnSomeOtherField = MyBase.Columns(FIELD_SomeOther)
        End Sub
    
        <DebuggerNonUserCodeAttribute()>
        Public Function NewtblMyTableRow() As tblMyTableRow
            Return CType(Me.NewRow, tblMyTableRow)
        End Function
    
        <DebuggerNonUserCodeAttribute()>
        Protected Overrides Function NewRowFromBuilder(ByVal builder As DataRowBuilder) As DataRow
            Return New tblMyTableRow(builder)
        End Function
    
        <DebuggerNonUserCodeAttribute()>
        Protected Overrides Function GetRowType() As Global.System.Type
            Return GetType(tblMyTableRow)
        End Function
    
        <DebuggerNonUserCodeAttribute()>
        Public Sub RemovetblMyTableRow(ByVal row As tblMyTableRow)
            Me.Rows.Remove(row)
        End Sub
    
    End Class
    
    '''<summary>
    '''Represents strongly named DataRow class.
    '''</summary>
    Partial Public Class tblMyTableRow
        Inherits DataRow
    
        Private tabletblMyTable As tblMyTable
    
        <DebuggerNonUserCodeAttribute()>
        Friend Sub New(ByVal rb As DataRowBuilder)
            MyBase.New(rb)
            Me.tabletblMyTable = CType(Me.Table, tblMyTable)
        End Sub
    
        <DebuggerNonUserCodeAttribute()>
        Public Property pID() As Integer
            Get
                Return CType(Me(Me.tabletblMyTable.pIDColumn), Integer)
            End Get
            Set(value As Integer)
                Me(Me.tabletblMyTable.pIDColumn) = value
            End Set
        End Property
    
        <DebuggerNonUserCodeAttribute()>
        Public Property SomeOtherField() As String
            Get
                Return CType(Me(Me.tabletblMyTable.SomeOtherFieldColumn), String)
            End Get
            Set(value As String)
                Me(Me.tabletblMyTable.SomeOtherFieldColumn) = value
            End Set
        End Property
    End Class
    

    That's all that you need. Perhaps It could be reduced, but then the dataset functions would not work as expected.

    If you want that code, generated automagically for you by the ID (VS2010) you must follow those steps:

    1. On server explorer, create the connection to your favorite DB
    2. Right-click on top of your project and select adding a new element.
    3. Just pick the dataset object template, the name is irrelevant. It will open in designer view.
    4. Pick the table from the database and drag to the dataset designer.
    5. Then... look at the class selector on top.
    6. Unfold and locate [yourTableName]Datatable. Click on it.
    7. It will jump to the said class in the DataSet1.designer.vb (cs) file.
    8. The next class it's the row definition. Just copy-paste them into a new class file.
    9. If you want a more complete datatable object, the next class below the row class define events, and the delegate it's just above the table def.

    Simple and I tested it to work in conjunction of the remaining program, that uses untyped. Perhaps it would be like polishing a turd, but I would like to add data annotations somewhere to do some client validations like in EF. And perhaps replace the columns constructor parameters for them. (but I caaan't)

    good Luck.