locked
How to sucessfully inherit from DataSet, DataTable and DataRow RRS feed

  • Question

  • Because Visual Studio .NET has almost exclusively been used as the source of strongly typed DataSets, there is hardly any information available on the correct way to derive your own classes from the DataSet, DataTable and DataRow objects.

     

    I've been studying the strongly typed DataSet classes generated automatically by VS.NET to try and get an understanding of how they work, because I do not like using code generators unless I understand them at least as well as I understand my own code.

     

    I've been having difficulty in creating my own forms of these classes and was wondering if there's a "secret formula" documented anywhere that will help me.

     

    The main problem I experience with my attempts is an Invalid Cast Exception which occurs at runtime. My code is strict and explicit and compiles ok, but will not run, and the reason is always that my derived datatable can't be cast to/from the System.Data.DataTable, or my derived datarow can't be cast to/from the System.Data.DataRow, or the same problem with my derived dataset.

     

    Are there any essential properties and methods that need to be overridden to be successful? Are there any steps to creating classes derived from these that will cause invalid cast exceptions if not properly implemented?

     

    I'm floundering here. Whilst I'm trust that the VS .NET tool provides all that is neccessary to produce strongly typed data-tier objects, I'd really like to learn to do it myself. That's just the way I am.

     

    Can anybody help, please? 

    Tuesday, June 19, 2007 3:54 PM

Answers

  • First, let me compliment you on a very nice post.  That's the kind of post that can be answered quickly. 

     

    It looks like the problem is that when the table collection has the default table instance added, that instance is of type DataTable, not of type tableType.  Although I can't seem to spot the code in the big post above that actually adds to MyBase.Tables, I'm sure that it must be adding a variable of type DataTable because this class definition does not allow for creating new instances of tableType.

     

    This is the big difference between the MS code and your code.  In the MS code, even though MyBase.Tables is a collection of DataTable objects, the actual values in the collection are of the derived datatable type.  This allows the cast statement to succeed because that particular datatable can indeed be cast into the derived datatable type.

     

    In your code, MyBase.Tables contains values of type DataTable and so they cannot be cast into a derived type.

     

    Does that make sense?  Now, on to the fix...

     

    Declare your classes as follows:

    Code Snippet
    Public
    Class DerivedDataSet(Of tableType As {DataTable, New})

     

    This will allow you to create new variables of type tableType so that you can add new tableType values to MyBase.Tables; and that will allow the cast to succeed.  So once you change your class declarations, proceed to change the type of any initial data objects that you create from the base DataObject type to the parameterType specified in your generic class definition.

     

    Hope that wraps this one up for you!

    Wednesday, June 27, 2007 4:41 PM
    Moderator
  • I think that what you are describing is the normal and expected behavior.  Even if you create a DataSet in the DataSet designer, its Tables collection will still return instances of a base DataTable.  Now, there will be a property in the DataSet for each DataTable that returns the strong type.  Through these properties you can access the strongly typed DataTable objects.

     

    But if you really want to do this, you could shadow the Tables property like this:

     

    Code Snippet

    Public Class DerivedDataSet

    Inherits DataSet

    Private mTables As New List(Of DerivedDataTable)

    Public Shadows ReadOnly Property Tables() As List(Of DerivedDataTable)

    Get

    Return Me.mTables

    End Get

    End Property

    End Class

     

     

    But this may not be a good idea...  Other objects that use the DataSet will be expecting the Tables collection to return base DataTable objects.  Using the code above could cause an Exception at runtime.  You should leave the Tables collection alone and provide Properties on your DataSet that expose each derived DataTable.

    Friday, August 3, 2007 12:29 AM
    Moderator
  •  

    I tried this and it opened up a can of worms. It'll force you to delve deeper and deeper into the .NET framework and get in a hell of a mess. I agree with RKimble. I'd leave it alone if I were you. Go with his first idea and provide a property to access the derived DataTable.
    Friday, August 3, 2007 8:06 AM

All replies

  • Perhaps you should post a simple example of what you've tried and mark the lines that throw the cast exceptions.

     

    There shouldn't be any hidden gotchas...

    Wednesday, June 20, 2007 12:53 AM
    Moderator
  • Thanks for your input. Sorry it took a while to get back to you. I've been on holiday.

    I am happy to post code, but I can't indicate where it's going wrong, because invalid cast exceptions by nature don't tell you why the cast is invalid. That's what's confusing me.

     

    I'm working towards a situation where I have a sort of generic (with a little "g") sort of dataset, datatable, and datarow which are abstract and provide functionality that makes it easy to provide strongly typed read and write access to their fields in derived classes. I want to do this for a number of reasons:

    1. To avoid having 600 lines of autogenerated code rewritten for every strongly typed dataset
    2. Because I want to really understand what the code generator is doing, as there is very little information available about strongly typing datasets without the help of Visual Studio. I also want to try and improve it.
    3. Because I like to work with my own code, rather than other peoples.

    My code is below. The line that throws the error is marked, but I can't say why the error is thrown.

     

    I apologise in advance for there being a LOT of code, but I have been careful to comment it well.

    Thanks VERY much to anybody who has the patience to look at it!!!!

     

    Best wishes,

    Ben

     

    Code Snippet

    Namespace Harcourt.Data

    ''' <summary>

    ''' Lays out the framework for data driven objects in the data tier. They are

    ''' expected to provide their own CRUD (CReate/Update/Delete) functionality.

    ''' </summary>

    ''' <remarks>

    ''' This class should be abstract from any particular data provider tool, so it

    ''' is supplied with an object derived from the DataProviderTool base class

    ''' in order to conduct operations specific to a particular database server,

    ''' such as (but not limited to) a Microsft SQL Server Database. It is hoped

    ''' that this class will facilitiate its derived classes in being strongly-typed.

    ''' </remarks>

    Public MustInherit Class DataSet _

    (Of tableType As DataTable(Of tableType, _

    rowType), _

    rowType As DataRow(Of tableType, rowType))

     

    Inherits System.Data.DataSet

     

     

    Private myDataProviderTool As DataProviderTool

    'provide particular types of commands, parameters and connections for

    'a given database server. For example, classes inheriting from

    'BaseDataDrivenObject might use a SqlDataProviderTool which would ensure that

    'all the parameters it created were of type SqlParameter, but without the

    'data driven class needing to be aware of the underlying data provider tool.

     

    Private myDefaultTable As tableType

    Private mySelectCommandText As String

    Private mySelectCommandParameters() As IDataParameter

    Private myIsInitialized As Boolean = False

     

     

     

    ''' <summary>

    ''' Constructor for this class

    ''' </summary>

    ''' <param name="selectCommandParameters">

    ''' An array of IDataParameters required for populating

    ''' </param>

    ''' <param name="selectCommandText">The command text to populate this object from the

    ''' database. Normally a stored procedure name.</param>

    ''' <param name="dataProviderTool">An object (implementing the IDataProviderTool

    ''' interface) that is used to interact with a particular database management

    ''' system.</param>

    ''' <remarks>Separated from the Init subroutine so that inheriting classes can

    ''' choose to populate the object conditionally after its instantiation,

    ''' allowing for functionality such as caching of datasets.</remarks>

    Protected Sub New( _

    ByVal dataProviderTool As DataProviderTool, _

    ByVal selectCommandText As String, _

    Optional ByVal selectCommandParameters As IDataParameter() = Nothing)

    myDataProviderTool = dataProviderTool

    mySelectCommandText = selectCommandText

    mySelectCommandParameters = selectCommandParameters

    End Sub

     

    ''' <summary>

    ''' Constructor for serialization/deserialization.

    ''' </summary>

    ''' <remarks></remarks>

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

    MyBase.New(info, context, False)

    If Me.IsBinarySerialized(info, context) Then

    Me.InitVars(False)

    End If

    End Sub

     

     

     

     

    ''' <summary>

    ''' Populates this DataSet with information from database by running the

    ''' specified select command. Infers an XML Schema if one is supplied.

    ''' </summary>

    ''' <param name="xmlSchemaReader">An optional XmlDataReader object from which to read the XML

    ''' Schema to be used to strongly type this DataSet.

    ''' </param>

    ''' <remarks>Separated from the constructor subroutine so that inheriting classes can

    ''' choose to populate the object conditionally separately from its instantiation,

    ''' allowing for functionality such as caching of datasets.</remarks>

    Protected Overloads Sub Init( _

    Optional ByVal xmlSchemaReader As XmlReader = Nothing)

    'Make sure that this object hasn't already been initialized.

    If Not myIsInitialized Then

    Me.Populate()

    'Infer an XML Schema if one is provided

    If Not xmlSchemaReader Is Nothing Then

    Try

    Me.InferXmlSchema(xmlSchemaReader, New String() {})

    Catch ex As Exception

    Throw New Exception(String.Format("BaseDataDrivenObject could not infer " & _

    "the XML schema supplied in {0}.", xmlSchemaReader))

    End Try

    End If

    'Flag that this object has been initialized

    myIsInitialized = True

    Else

    Throw New InvalidOperationException(String.Format("The {0} object has already " & _

    "been initialized.", Me))

    End If

    End Sub

     

    ''' <summary>

    ''' Populates this DataSet with information from database by running the

    ''' specified select command. Infers an XML Schema if one is supplied.

    ''' </summary>

    ''' <param name="xmlSchemaFile">An optional XmlDataReader object from which to read the XML

    ''' Schema to be used to strongly type this DataSet.

    ''' </param>

    ''' <remarks>Separated from the constructor subroutine so that inheriting classes can

    ''' choose to populate the object conditionally separately from its instantiation,

    ''' allowing for functionality such as caching of datasets.</remarks>

    Protected Overloads Sub Init( _

    ByVal xmlSchemaFile As String)

    'Make sure that this object hasn't already been initialized.

    If Not myIsInitialized Then

    Me.Populate()

    Try

    Me.InferXmlSchema(xmlSchemaFile, New String() {})

    Catch ex As Exception

    Throw New Exception(String.Format("BaseDataDrivenObject could not infer " & _

    "the XML schema at {0}.", xmlSchemaFile))

    End Try

    'Flag that this object has been initialized

    myIsInitialized = True

    Else

    Throw New InvalidOperationException(String.Format("The {0} object has already " & _

    "been initialized.", Me))

    End If

    End Sub

     

    ''' <summary>

    ''' Sets up the columns on the table.

    ''' </summary>

    ''' <remarks></remarks>

    Friend Overloads Sub InitVars(Optional ByVal initTable As Boolean = True)

    If Not MyBase.Tables(0) Is Nothing Then

    myDefaultTable = CType(MyBase.Tables(0), tableType)

    If initTable Then

    myDefaultTable.InitClass(myDefaultTable.MakeColumns())

    End If

    End If

    End Sub

     

    ''' <summary>

    ''' Populates this object with information from database.

    ''' </summary>

    ''' <remarks></remarks>

    Private Sub Populate()

    'Make sure myCommandText is not blank

    If Len(mySelectCommandText) > 0 Then

    'Run the procedure to populate the object.

    myDataProviderTool.RunProcedure( _

    mySelectCommandText, _

    mySelectCommandParameters, _

    Me)

    Else

    'No select command text specified

    Throw New Exception(String.Format("{0} object cannot populate itself because " & _

    "no command text was specified. This parameter should be passed in the " & _

    "constructor.", Me))

    End If

    End Sub

     

    ''' <summary>

    ''' Makes a clone of the current instance.

    ''' </summary>

    ''' <remarks></remarks>

    Public Overrides Function Clone() As System.Data.DataSet

    Dim cln As Harcourt.Data.DataSet(Of tableType, rowType) = _

    CType(MyBase.Clone, Harcourt.Data.DataSet(Of tableType, rowType))

    cln.InitVars()

    cln.SchemaSerializationMode = Me.SchemaSerializationMode

    Return cln

    End Function

     

    ''' <summary>

    ''' Exposes the default table.

    ''' </summary>

    ''' <remarks></remarks>

    Public ReadOnly Property DefaultTable() As tableType

    Get

    Return (myDefaultTable)

    End Get

    End Property

     

     

    End Class

     

     

     

    ''' <summary>

    ''' Class providing functionality to make it easier to strongly type data tables.

    ''' </summary>

    ''' <remarks></remarks>

    Public MustInherit Class DataTable _

    (Of tableType As Harcourt.Data.DataTable(Of tableType, rowType), _

    rowType As DataRow(Of tableType, rowType))

    Inherits DataTable

     

    ''' <summary>

    ''' Constructor for this class

    ''' </summary>

    Public Sub New(Optional ByVal tableName As String = "")

    MyBase.New()

    If Len(tableName) > 0 Then

    Me.TableName = tableName

    End If

    Me.BeginInit()

    'Make sure object contains all the typed-columns specified by the

    'derived class

    Me.InitClass(Me.MakeColumns())

    Me.EndInit()

    End Sub

     

    ''' <summary>

    ''' Constructor to build from a System.Data.DataTable

    ''' </summary>

    Protected Friend Sub New(ByVal table As System.Data.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

     

    ''' <summary>

    ''' Constructor for serialization/deserialization.

    ''' </summary>

    ''' <param name="info">An instance of SerializationInfo to use to construct the instance

    ''' of this class.</param>

    ''' <param name="context">The streaming context</param>

    ''' <remarks>Protected so it cannot be otherwise used.</remarks>

    Protected Sub New( _

    ByVal info As SerializationInfo, _

    ByVal context As StreamingContext)

    MyBase.New(info, context)

    'Cast the columns as strongly-typed columns.

    Me.InitVars(Me.MakeColumns)

    End Sub

     

     

    ''' <summary>

    ''' Lays out the structure of the table using the array of columns provided by the

    ''' derived class.

    ''' </summary>

    ''' <remarks></remarks>

    Protected Friend Sub InitClass(ByVal columns() As Harcourt.Data.DataColumn)

    For Each currentColumn As Harcourt.Data.DataColumn In columns

    Me.Columns.Add(currentColumn)

    Next

    End Sub

     

    ''' <summary>

    ''' Casts all the columns of the base class as strongly-typed DataColumns

    ''' according to the list of columns specified in the derived class.

    ''' </summary>

    ''' <remarks></remarks>

    Protected Friend Sub InitVars(ByVal columns() As Harcourt.Data.DataColumn)

    For Each currentColumn As Harcourt.Data.DataColumn In Columns

    currentColumn = CType _

    (MyBase.Columns(currentColumn.ColumnName), Harcourt.Data.DataColumn)

    Next

    End Sub

     

    ''' <summary>

    ''' Returns the type of the row used in this table.

    ''' </summary>

    ''' <remarks></remarks>

    Protected Overrides Function GetRowType() As Type

    Return GetType(rowType)

    End Function

     

    ''' <summary>

    ''' Gets a new row of specified rowType using the DataRowBuilder. Cannot be fully implemented

    ''' at this level because generic types cannot be constructed with parameters.

    ''' </summary>

    ''' <remarks></remarks>

    Protected MustOverride Overrides Function NewRowFromBuilder(ByVal builder As DataRowBuilder) _

    As System.Data.DataRow

    'Would look something like: Return New BookRow(builder)

     

     

    ''' <summary>

    ''' Creates a new row of type rowType with the same schema as the table.

    ''' </summary>

    ''' <returns></returns>

    ''' <remarks></remarks>

    Public Shadows Function NewRow() As rowType

    Return CType(MyBase.NewRow(), rowType)

    End Function

     

    ''' <summary>

    ''' Adds a row whose schema corresponds with that of the table to the table

    ''' and returns it.

    ''' </summary>

    ''' <param name="row">The row instance to add.</param>

    ''' <returns></returns>

    ''' <remarks></remarks>

    Protected Friend Function AddRow( _

    ByVal row As rowType) _

    As rowType

    MyBase.Rows.Add(row)

    Return row

    End Function

     

    ''' <summary>

    ''' Creates a new row of type rowType with the same schema as the table.

    ''' </summary>

    ''' <remarks></remarks>

    Protected Friend Sub AddColumn(ByVal column As Harcourt.Data.DataColumn)

    'Add the column to the underlying DataTable

    MyBase.Columns.Add(column)

    End Sub

     

    ''' <summary>

    ''' The number of rows in the table.

    ''' </summary>

    ''' <value></value>

    ''' <returns>Integer. The number of rows in the table.</returns>

    ''' <remarks></remarks>

    Public ReadOnly Property Count() As Integer

    Get

    Return Me.Rows.Count

    End Get

    End Property

     

    ''' <summary>

    ''' Gets a row in the table at the given index.

    ''' </summary>

    ''' <param name="index">The index at which to retrieve the row.</param>

    ''' <value></value>

    ''' <returns>An instance of rowType.</returns>

    ''' <remarks></remarks>

    Default Public ReadOnly Property Item(ByVal index As Integer) As rowType

    Get

    Return CType(Me.Rows(index), rowType)

    End Get

    End Property

     

    ''' <summary>

    ''' In derived classes, returns an array of typed columns that should be

    ''' created in the table when it is created.

    ''' </summary>

    ''' <returns>An array of Harcourt.Data.DataColumn</returns>

    ''' <remarks></remarks>

    Protected Friend MustOverride Function MakeColumns() As Harcourt.Data.DataColumn()

     

    End Class

     

    ''' <summary>

    ''' Class providing functionality to make it easier to strongly type data rows.

    ''' </summary>

    ''' <remarks></remarks>

    Public MustInherit Class DataRow _

    (Of tableType As DataTable(Of tableType, rowType), _

    rowType As DataRow(Of tableType, rowType))

    Inherits System.Data.DataRow

     

    Private myDataTable As tableType

     

     

    ''' <summary>

    ''' Constructor for this class.

    ''' </summary>

    ''' <param name="builder"></param>

    ''' <remarks></remarks>

    Public Sub New(ByVal builder As DataRowBuilder)

    MyBase.New(builder)

    myDataTable = CType(MyBase.Table, tableType)

    End Sub

     

     

    ''' <summary>

    ''' Facilitates getting the value from a field.

    ''' </summary>

    ''' <remarks></remarks>

    Protected Overridable Function GetValueFromColumn(Of T) _

    (ByVal column As Harcourt.Data.DataColumn) As T

    If TypeOf (column.DataType) Is T Then

    Return CType(Me(column), T)

    Else

    Throw New Exception(String.Format("Cannot get a value of type {0} from the {1} " & _

    "column as the column's datatype is {2}.", _

    GetType(T), _

    column, _

    column.DataType))

    End If

    End Function

     

    ''' <summary>

    ''' Facilitates changing the value in a field.

    ''' </summary>

    ''' <remarks></remarks>

    Protected Overridable Sub SetColumnValueTo(Of T)( _

    ByVal column As Harcourt.Data.DataColumn, _

    ByVal value As T)

    If TypeOf (column.DataType) Is T Then

    Me(column) = value

    Else

    Throw New Exception(String.Format("Cannot set the {0} column to a value of type " & _

    "{1} as the column's datatype is {2}.", column.ColumnName, GetType(T), column.DataType))

    End If

    End Sub

     

    End Class

     
     

    Public Class DataColumn

    Inherits System.Data.DataColumn

     

    ''' <summary>

    ''' Constructor for this class.

    ''' </summary>

    ''' <param name="columnName">The name of the column.</param>

    ''' <param name="dataType">The data type of the column.</param>

    ''' <param name="allowDbNull">Whether or not to allow null values.</param>

    ''' <param name="isUnique">Whether or not values in the column must be unique.</param>

    ''' <remarks></remarks>

    Public Sub New( _

    ByVal columnName As String, _

    ByVal dataType As Type, _

    Optional ByVal allowDbNull As Boolean = True, _

    Optional ByVal isUnique As Boolean = False)

    MyBase.New(columnName, dataType)

    'Make sure that developer doesn't combine allowDbNull with isUnique

    If allowDbNull And isUnique Then

    Throw New Exception("Null values are not allowed in columns that must contain " & _

    "unique values.")

    End If

    MyBase.AllowDBNull = allowDbNull

    MyBase.Unique = isUnique

    End Sub

    End Class

     

     

    End Namespace

     

     

    'And now the derived classes, to show how the base classes should be used. In this case, they store information about books: Title, ISBN and Author.

     

    Namespace Harcourt.OnixXml.Data

    ''' <summary>

    ''' Lays out the CRUD (CReate/Update/Delete) functionality for book information

    ''' in the database.

    ''' </summary>

    ''' </remarks>

    Public Class BookDataSet

    Inherits Harcourt.Data.DataSet _

    (Of BookTable, _

    BookRow)

    ''' <summary>

    ''' Constructor for this class.

    ''' </summary>

    ''' <param name="dataProviderTool">An object (implementing IDataProviderTool

    ''' interface) that is used to interact with a particular database management

    ''' system.</param>

    Public Sub New( _

    ByVal dataProviderTool As DataProviderTool)

    'Just create the DataSet and assign it a provider tool.

    MyBase.New(dataProviderTool, "GetBooks", Nothing)

    'Now populate it with information about books from the database.

    MyBase.Init()

    End Sub

     

    ''' <summary>

    ''' Default property gets an instance of BookRow from the first table in this

    ''' DataSet

    ''' </summary>

    ''' <param name="index"></param>

    ''' <value></value>

    ''' <returns></returns>

    ''' <remarks></remarks>

    Default Public ReadOnly Property Book(ByVal index As Integer) As BookRow

    Get

    Return CType(CType(Me.Tables(0), BookTable).Rows(index), BookRow)

    End Get

    End Property

     

    ''' <summary>

    '''

    ''' </summary>

    ''' <param name="isbn"></param>

    ''' <value></value>

    ''' <returns></returns>

    ''' <remarks></remarks>

    Default Public ReadOnly Property Book(ByVal isbn As String) As BookRow

    Get

    Return CType(Me.Tables(0).Rows.Find(isbn), BookRow)

    End Get

    End Property

     

     

    End Class

     

     

     

    Public Class BookTable

    Inherits Harcourt.Data.DataTable(Of BookTable, BookRow)

     

    Public Sub New()

    MyBase.New("Books")

    End Sub

     

    ''' <summary>

    ''' Creates a new row of type BookRow

    ''' </summary>

    ''' <remarks></remarks>

    Protected Overrides Function NewRowFromBuilder(ByVal builder As System.Data.DataRowBuilder) _

    As System.Data.DataRow

    Return New BookRow(builder)

    End Function

     

    ''' <summary>

    ''' Array of all the columns that form the structure of this table. Constructor of base

    ''' class applies this column layout to the table.

    ''' </summary>

    ''' <remarks></remarks>

    Protected Overrides Function MakeColumns() As DataColumn()

    Return New Harcourt.Data.DataColumn() { _

    New DataColumn("Isbn", GetType(String), False, True), _

    New DataColumn("Title", GetType(String), False, True), _

    New DataColumn("Author", GetType(String), True, False) _

    }

    End Function

     

    ''' <summary>

    ''' Strongly-typed public exposition of a particular column in the DataTable

    ''' </summary>

    ''' <remarks></remarks>

    Public ReadOnly Property IsbnColumn() As DataColumn

    Get

    Return CType(MyBase.Columns(0), Harcourt.Data.DataColumn)

    End Get

    End Property

    ''' <summary>

    ''' Strongly-typed public exposition of a particular column in the DataTable

    ''' </summary>

    ''' <remarks></remarks>

    Public ReadOnly Property TitleColumn() As DataColumn

    Get

    Return CType(MyBase.Columns(1), Harcourt.Data.DataColumn)

    End Get

    End Property

    ''' <summary>

    ''' Strongly-typed public exposition of a particular column in the DataTable

    ''' </summary>

    ''' <remarks></remarks>

    Public ReadOnly Property AuthorColumn() As DataColumn

    Get

    Return CType(MyBase.Columns(2), Harcourt.Data.DataColumn)

    End Get

    End Property

     

    End Class

     

     

     

    Public Class BookRow

    Inherits DataRow(Of BookTable, BookRow)

    'Inherits DataRow(Of BookTable, BookRowCollection, BookRow)

     

    Public Sub New(ByVal builder As DataRowBuilder)

    MyBase.New(builder)

    End Sub

     

    ''' <summary>

    ''' Public exposition of the value in a particular field in this row.

    ''' </summary>

    ''' <remarks></remarks>

    Public Property Isbn() As String

    Get

    Return GetValueFromColumn(Of String)(CType(Me.Table, BookTable).IsbnColumn)

    End Get

    Set(ByVal value As String)

    SetColumnValueTo(Of String)(CType(Me.Table, BookTable).IsbnColumn, value)

    End Set

    End Property

     

    ''' <summary>

    ''' Public exposition of the value in a particular field in this row.

    ''' </summary>

    ''' <remarks></remarks>

    Public Property Title() As String

    Get

    Return GetValueFromColumn(Of String)(CType(Me.Table, BookTable).TitleColumn)

    End Get

    Set(ByVal value As String)

    SetColumnValueTo(Of String)(CType(Me.Table, BookTable).TitleColumn, value)

    End Set

    End Property

     

    ''' <summary>

    ''' Public exposition of the value in a particular field in this row.

    ''' </summary>

    ''' <remarks></remarks>

    Public Property Author() As String

    Get

    Return GetValueFromColumn(Of String)(CType(Me.Table, BookTable).AuthorColumn)

    End Get

    Set(ByVal value As String)

    SetColumnValueTo(Of String)(CType(Me.Table, BookTable).AuthorColumn, value)

    End Set

    End Property

     

    End Class

     

     

     

     

    End Namespace

     

     

     

    'Finally, the test module

     

     

    Module [Default]

    Sub Main()

     

    Dim provider As New SqlDataProviderTool("Data Source=XXXX;" & _

    "Initial Catalog=XXXX;Integrated Security=True")

    Dim books As New BookDataSet(provider)

    For Each row As Harcourt.OnixXml.Data.BookRow In books.DefaultTable.Rows

    Console.WriteLine(row.Isbn & _

    ", " & row.Title & _

    ", " & row.Author)

    Next

    'Set break point here so you can see what's happened.

    Console.WriteLine("Done")

    End Sub

    End Module

     

    Monday, June 25, 2007 11:39 AM
  • I've stripped this down to the simplest possible level.

     

    If I create a class that derives from DataTable but adds no further functionality at all, the cast still won't work.

    If I can't make derivatives of DataTable that do nothing, it's no wonder the monster project above won't work!

     

    Does anybody have any advice, please?

     

     

    Code Snippet

    Public Class DataTableWithNothingExtra

    Inherits System.Data.DataTable

     

    'Does and says NOTHING that a DataTable doesn't already.

    End Class

     

     

    Module Mod1

     

    Sub Main()

    'This throws a cast exception.

     Dim test As DataTableWithNothingExtra = CType(New DataTable(), DataTableWithNothingExtra)

    End Sub

     

    End Module

     

    Monday, June 25, 2007 2:42 PM
  •  BenCh1 wrote:

    I've stripped this down to the simplest possible level.

     

    If I create a class that derives from DataTable but adds no further functionality at all, the cast still won't work.

    If I can't make derivatives of DataTable that do nothing, it's no wonder the monster project above won't work!

     

    Does anybody have any advice, please?

     

     

    Code Snippet

    Public Class DataTableWithNothingExtra

    Inherits System.Data.DataTable

     

    'Does and says NOTHING that a DataTable doesn't already.

    End Class

     

     

    Module Mod1

     

    Sub Main()

    'This throws a cast exception.

     Dim test As DataTableWithNothingExtra = CType(New DataTable(), DataTableWithNothingExtra)

    End Sub

     

    End Module

     

     

    Ok, this simple example makes the problem clear - you can only cast up an inheritence chain, not down one!  To explain further:

     

    DataTableWithNothingExtra inherits from DataTable.  So you can cast DataTableWithNothingExtra into a DataTable.  But you cannot cast a DataTable into a DataTableWithNothingExtra.  That because your derived type may contain properties and methods that the base type does not have - so the cast is not valid.

     

    Now, if you create a variable as type DataTable, and set it equal to an instance of DataTableWithNothingExtra, then you will be able to cast the variable back - so long as it continues to point to the correct type in memory.  That said, here's an example:

     

    Dim dt1 As DataTableWithNothingExtra

    Dim dt As New DataTable

    dt1 = CType(dt, DataTableWithNothingExtra)

     

    This code fails as you've seen.  However:

     

    Dim dt1 As New DataTableWithNothingExtra

    Dim dt As DataTable

    dt = dt1

    dt1 = CType(dt, DataTableWithNothingExtra)

     

    This code will work because eventhough the variable dt is of type DataTable, it points to a DataTableWithNothingExtra in memory.

     

    Does that help explain what's happening?

    (PS as a side note, the example above is a case where DirectCast should be used instead of CType when you know that dt will contain an appropriate datatype).

     

    Monday, June 25, 2007 10:04 PM
    Moderator
  • Yes, that's very helpful, and it makes perfect sense, now that you put it like that.

    Thank you.

     

    It doesn't, however, make it easy to see how to correct the problem. My first impression would be that I need to find a way to make the Tables property of my derived dataset return a collection of my derived tables, and also make the Rows property of my derived tables return a collection of my derived rows. That way, I'll be able to read them thus:

     

    Code Snippet

    Dim derivedTable As DerivedTable = DerivedDataSet.Tables(0)

     

    For Each derivedRow As DerivedRow In derivedTable.Rows

        Console.WriteLine(row.ExtraProperty)

        Console.WriteLine(row.OtherExtraProperty)

    Next

     

    This doesn't strike me as an easy thing to do, because Tables and Rows properties aren't overridable, and the DataRowCollection and DataTableCollection classes aren't inheritable.

     

    Upon examining the autogenerated code in Visual Studio further, I can't see any evidence that this has been done, so I'm still a bit lost. Am I thinking along the wrong lines to achieve what I want?

    Tuesday, June 26, 2007 8:31 AM
  • Hi ben,

     

    Out of that behemoth mess of code you posted, can you pickout just the code block that is throwing the cast exception?  Mark or highlight the line of code that the IDE breaks on when the exception occurs.

     

    As stated, there shouldn't be any hidden issues with inheriting data objects.  If you can point out the exact lines of code that are failing, we may be able to suggest a different approach that will work.

    Tuesday, June 26, 2007 3:01 PM
    Moderator
  • Hiya again,

     

    Sorry for posting so much VB code. It's a project I've been working on for a while so naturally many classes have become involved to try and encapsulate as much logic as possible. I'll try and be more specific.

     

    I think where I've got confused is that when I study Microsoft's autogenerated code, I notice it casts an ordinary DataTable as the derived table type, in order that it can be accessed via a property of the derived dataset object. The code below illustrates what I mean, and is taken from MS's autogen code.

    Code Snippet
          _
        Friend Overloads Sub InitVars(ByVal initTable As Boolean)
            Me.tableUSP_HTD_GetAllSubjects = CType(MyBase.Tables("USP_HTD_GetAllSubjects"),USP_HTD_GetAllSubjectsDataTable)
            If (initTable = true) Then
                If (Not (Me.tableUSP_HTD_GetAllSubjects) Is Nothing) Then
                    Me.tableUSP_HTD_GetAllSubjects.InitVars
                End If
            End If
        End Sub

     

    So, in my custom dataset, I've exposed the custom table in the same way (except that I've called it myDefaultTable and access it through the property DefaultTable. I've also had to cast this table up from a System.DataTable, thus. The first cast problem encountered is when setting the private member variable myDefaultTable equal to the first DataTable in the Tables collection, cast as the derived table type, as highlighted below.

     

    Code Snippet

    Public Class DerivedDataSet(Of tableType As System.DataTable)

           Inherits DataSet

     

           'Omitted some code for brevity ....

     

            Protected Overloads Sub InitVars(Optional ByVal initTable As Boolean = True)

                If Not MyBase.Tables(0) Is Nothing Then

     

                   'The following line gives me an invalid cast error,

                   'and as you explain, it must be because I'm casting UP the

                   'inheritance chain rather than down it. Nonetheless,

                   'I've just copied the autogenerated code, so if they

                   'can do it, why can't I?

     

                    myDefaultTable = CType(MyBase.Tables(0), tableType)

                    If initTable AndAlso Not myDefaultTable Is Nothing Then
                        myDefaultTable.InitClass(myDefaultTable.MakeColumns())
                    End If

                End If

            End Sub

     

           'Omitted some more code

     

           Public ReadOnly Property DefaultTable As tableType

               Get

                  Return myDefaultTable

               End Get

           End Property

     

    End Class

     

    I suppose then, the problem is to do with my derived table classes? Do you think?

    Wednesday, June 27, 2007 9:43 AM
  • First, let me compliment you on a very nice post.  That's the kind of post that can be answered quickly. 

     

    It looks like the problem is that when the table collection has the default table instance added, that instance is of type DataTable, not of type tableType.  Although I can't seem to spot the code in the big post above that actually adds to MyBase.Tables, I'm sure that it must be adding a variable of type DataTable because this class definition does not allow for creating new instances of tableType.

     

    This is the big difference between the MS code and your code.  In the MS code, even though MyBase.Tables is a collection of DataTable objects, the actual values in the collection are of the derived datatable type.  This allows the cast statement to succeed because that particular datatable can indeed be cast into the derived datatable type.

     

    In your code, MyBase.Tables contains values of type DataTable and so they cannot be cast into a derived type.

     

    Does that make sense?  Now, on to the fix...

     

    Declare your classes as follows:

    Code Snippet
    Public
    Class DerivedDataSet(Of tableType As {DataTable, New})

     

    This will allow you to create new variables of type tableType so that you can add new tableType values to MyBase.Tables; and that will allow the cast to succeed.  So once you change your class declarations, proceed to change the type of any initial data objects that you create from the base DataObject type to the parameterType specified in your generic class definition.

     

    Hope that wraps this one up for you!

    Wednesday, June 27, 2007 4:41 PM
    Moderator
  • A sub conversation about whether to use Typed Datasets at all, which began in this thread, has been split out and moved to http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1791490&SiteID=1&mode=1
    Wednesday, June 27, 2007 10:05 PM
    Moderator
  • Thank you very much!

     

    Sorry it took a while to get back to you, but work has been very busy this week.

    Things are moving on much faster since the help you gave me.

     

    I'll mark this question as answered.

     

    On another note, it would have been easier to implement my solution if .NET generics supported parameters in the constructors of generic types.

     

    e.g.

     

    Code Snippet

    Public Class Foo(Of T As {Bar, New(String)})

     

    Public Sub New(ByVal str As String)

        Dim bla as New T(str)

     

        'etc etc.

    End Sub

     

    End Class

     

    I can see this being a useful feature. Can anybody tell me if there are plans to introduce similar syntax in future releases?

     

    Ben

     

    Friday, July 6, 2007 8:26 AM
  • Hello:

     

    I'm some confused at all. I would inherit from DataSet,DataTable and DataRow. I successfull inherit from DataRow and automatic instance this DervivedDataRow from DataTable but i can't do the same with DataSet. DerivedDataSet always instance DataTable and not DerivedDataTable.

     

    My simple code is:

     

     

    Code Snippet

    Public Class DervideDataRow

    Inherits DataRow

     

    Friend Sub New(ByVal rb As DataRowBuilder)

    MyBase.New(rb)

    End Sub

     

    Public _ExtendedVarExample As String = "DataRowProperty"

     

    End Class

     

    Public Class DerivedDataTable

    Inherits DataTable

     

    Protected Overrides Function NewRowFromBuilder(ByVal builder As System.Data.DataRowBuilder) As System.Data.DataRow

     

    Return New DervideDataRow(builder)

     

    End Function

     

    Public _ExtendedVarExample As String = "OKDataTablaProperty"

     

    End Class

     

     

     

    The dude is witch code is necesary here for this DerivedDataSet instance DerviedDataTables in tipical fill, .. operations. I'm not sure in declaration at all...

     

    Code Snippet

    Public Class DerivedDataSet

    Inherits DataSet

     

    ...???

     

    End Class

     

     

     

    Or this declaration:

     

    Code Snippet

    Public Class DerivedDataSet(Of tablatype As {DerivedDataTable, New})

    Inherits DataSet

     

    ...???

     

    End Class

     

     

    The obvius problem: if i try with DervedDataSet ->  

     

    Adapter.Fill(ds)

     

    If typeof ds.tables(0) is DerviedDataTable then 'This is always false because is DataTable object and not DerivedDataTable.

     

    End If

     

     Help please..

     

    THCTASE '07

     

     

    Thursday, August 2, 2007 12:54 PM
  • I think that what you are describing is the normal and expected behavior.  Even if you create a DataSet in the DataSet designer, its Tables collection will still return instances of a base DataTable.  Now, there will be a property in the DataSet for each DataTable that returns the strong type.  Through these properties you can access the strongly typed DataTable objects.

     

    But if you really want to do this, you could shadow the Tables property like this:

     

    Code Snippet

    Public Class DerivedDataSet

    Inherits DataSet

    Private mTables As New List(Of DerivedDataTable)

    Public Shadows ReadOnly Property Tables() As List(Of DerivedDataTable)

    Get

    Return Me.mTables

    End Get

    End Property

    End Class

     

     

    But this may not be a good idea...  Other objects that use the DataSet will be expecting the Tables collection to return base DataTable objects.  Using the code above could cause an Exception at runtime.  You should leave the Tables collection alone and provide Properties on your DataSet that expose each derived DataTable.

    Friday, August 3, 2007 12:29 AM
    Moderator
  •  

    I tried this and it opened up a can of worms. It'll force you to delve deeper and deeper into the .NET framework and get in a hell of a mess. I agree with RKimble. I'd leave it alone if I were you. Go with his first idea and provide a property to access the derived DataTable.
    Friday, August 3, 2007 8:06 AM
  • Hello:
     
    This not solves my problem. May be i don't explains it well. My english is a bit poor.
     
    My objective is extend with inheritance the DataSet, DataTable and DataRow objects keeping the expected functionality of bases class.
     
    In my code example i successful extend table and datarow inheritances. When i do a Adapter.Fill(objDerviedDataTable) the DataRows of objDerviedDataTable are created by Adapter transparently of type DerivedDataRow (because i overriedes Function NewRowFromBuilder). I want the same functionality with DervivedDataSet. This is, when try Adapter.Fill(objDerivedDataSet) the DataTables should be of type DerviedDataTables.
     
    With your code i succesfull have a typed collection of DerivedDataTable but the object contained in it are type DataTable (created in adapter.Fill(objDerivedDataSet) operation).

     

    Even if you create a DataSet in the DataSet designer, its Tables collection will still return instances of a base DataTable. 

     

    I'm using untyped DataSet and don't want to use typed DataSet because i don't have database structure in design  time.

     

     

    But this may not be a good idea...  Other objects that use the DataSet will be expecting the Tables collection to return base DataTable objects.  Using the code above could cause an Exception at runtime.  You should leave the Tables collection alone and provide Properties on your DataSet that expose each derived DataTable.

     

    At all de DerivedDataTable is a DataTable and has the same functionality of a DataTable. I'm expect all objects that deals with DataTable can deal with DerivedDateTable because this one has only other new properties.
     
    I expect a similar function in DataSetthat  Function NewRowFromBuilder does in DataTable, but not exist a NewTableFromBuilder. 
     
    Thank you very much rkimble and BenCh1.

     

     

    Friday, August 3, 2007 9:27 AM
  • I see. Well I can't recommend this, because I haven't tried it, but I notice (looking at Lutz Roeder's .NET reflector) that the DataAdapter.Fill method is overridable.

     

    If that's what you want to do, you could look into deriving a class from DataAdapter and overriding the Fill method so that it will return a derived data table.

     

    If I was going down that road, I'd probably try to create a generic derived DataAdapter class, that would allow any kind of derived DataTable object to be created, but as I haven't researched it I don't know if that is feasible.

     

    Is that helpful?

    Friday, August 3, 2007 10:03 AM
  • Hello,  BenCh1:

     

    I think the way should be inheriting DataSet because SQLDataAdapter and SQLCEDataAdapter are final notoverridable class. As well i want to do it genereic respect DataAdapter. 

     

    DataAdapter option should be possible doing a Proxy Class (saving inheritance problem) and catpturing Fill operation in proxy and replacement Fill(DataSet) to some Fill(DataTable) but i think is could be too complex.

     

    Thanks for help.

     

    THCTASE '07

     

    Friday, August 3, 2007 10:29 AM