Return a DataTable from a WebMethod

Posted on 2002-07-22
Medium Priority
Last Modified: 2007-11-27
I have an XML schema which defines my DataSet.  So, I deal with a MyDataSet type, which inherits from Data.DataSet, and a MyDataTable, which inherits from Data.DataTable.  These classes were created using the .NET IDE's "Generate Dataset" function, to create all these strongly-typed classes that mirror the structure of my XSD.

I have a WebMethod in a Web Service which I desperately want to return one of these MyDataTable objects as a value.  The MyDataTable object I want to return is part of a MyDataSet object--but I only want to return this single MyDataTable object.  My WinClient.EXE needs to receive this MyDataTable object, one way or another.  Here's what I've run up against so far:

[1]  My first idea was to have the WebMethod (let's call it WebFoo()) return a type of MyDataTable.  This seems the most obvious route.  But when I try to update the Web Reference to WebFoo() from my WinClient, then it complains about "no default constructor for MyDataTable".  I don't know how I would address this problem, so I move on.

[2]  Then I thought, hey, these DataSet classes (both base and derived) have a handy .GetXml() method, which claims to return String data representing the DataSet.  So I tried this in WebFoo():

   <WebMethod(MessageName:="WebFoo")> _
   Public Function WebFoo( _
      ByVal sSomeKey As String _
      ) _
      As String

      Dim oNewDataSet As New MyDataSet()
      '// First, we need to remove any (empty) table by the name of the one
      '// that we're about to add.  Otherwise, the Tables.Add() call below
      '// will blow sky-high.
      If (oNewDataSet.Tables.Contains(OldDataSet.MyDataTable.TableName)) Then
         Call oNewDataSet.Tables.Remove(OldDataSet.MyDataTable.TableName)
      End If

     '// If I don't use the .Clone() method here, execution fails with a
      '// message to the effect that, "MyDataTable is already a Table in
      '// the OldDataSet DataSet."
      '//    On the other hand, this .Clone method doesn't even include
      '// any rows--just the DataTable's structure.  Aaaaaugggh!
      Call oNewDataSet.Tables.Add(OldDataSet.MyDataTable.Clone)

      Dim sReturnValue As String = oNewRMXDataSet.GetXml()
      '// The only thing returned here is this:
      '//    <MyDataSet xmlns="http://tempuri.org/MyDataSet.xsd" />
      Return sReturnValue

   End Function

See my comments in the above code--I'm hamstrung by [a] the Tables.Add() method (which seems to want the result of the DataTable.Clone() method), [b] the DataTable.Clone() method, which seems to ignore row data, and only clones the structure, and [c] the DataSet.GetXml() method, which does not return the whole DataSet, but only the first line thereof.

[3]  DarthPedro suggested I just return the MyDataSet object (since this would indeed have the default constructor necessary), but I still have to add the MyDataTable object to that return-value MyDataSet object before trying to return it, and I can't add MyDataTable to it because no rows get added.  Yeeesh.  I know I have to be making this harder than it really should be.

I guess I could loop through each row in the OldDataSet's MyDataTable, adding each one to the NewDataSet's MyDataTable, but at some point I have to think there's an easier (cleaner, more elegant) way of doing all this that I just don't know about.  Anyone out there know of such a way?

Thanks a lot.
Question by:bryker
  • 9
  • 2
  • 2
  • +2
LVL 23

Expert Comment

ID: 7170139
Can you post your MyDataTable class code? Have you implemented Clone method  on it or are you relying on base class's implementation. Since its a strongly typed table, you may have to implement clone method but i am not sure about that. Also if you can post the schema file, then i can try duplcating your problem.

Author Comment

ID: 7170198

Sure.  Here's the schema, trimmed down to just include the one table that I want to return.  The other tables would seem to be extraneous to include here, so I clipped them out...so just know that there ARE more tables than just this one.

   <?xml version="1.0" encoding="utf-8" ?>
   <xs:schema id="RMXDataModel" targetNamespace="http://tempuri.org/RMX10DataModel.xsd" elementFormDefault="qualified"    xmlns="http://tempuri.org/RMX10DataModel.xsd" xmlns:mstns="http://tempuri.org/RMX10DataModel.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema"    xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" version="1.0">
      <xs:element name="BaseCaseInfo">
                        <xs:element name="CalibrationKey" type="xs:string" />
                        <xs:element name="BaseCaseKey" type="xs:string" />
                        <xs:element name="ModelKey" type="xs:string" />
                        <xs:element name="CalibrationState" type="xs:short" />
                        <xs:element name="BaseCaseType" type="xs:long" />
                        <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" />
            <xs:key name="BaseCaseInfoKey" msdata:PrimaryKey="true">
                  <xs:selector xpath="." />
                  <xs:field xpath="mstns:CalibrationKey" />

The DataTable class code:

    <System.Diagnostics.DebuggerStepThrough()>  _
    Public Class BaseCaseInfoDataTable
        Inherits DataTable
        Implements System.Collections.IEnumerable
        Private columnCalibrationKey As DataColumn
        Private columnBaseCaseKey As DataColumn
        Private columnModelKey As DataColumn
        Private columnCalibrationState As DataColumn
        Private columnBaseCaseType As DataColumn
        Friend Sub New()
        End Sub
        Friend Sub New(ByVal table As DataTable)
            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
            Me.DisplayExpression = table.DisplayExpression
        End Sub
        <System.ComponentModel.Browsable(false)>  _
        Public ReadOnly Property Count As Integer
                Return Me.Rows.Count
            End Get
        End Property
        Friend ReadOnly Property CalibrationKeyColumn As DataColumn
                Return Me.columnCalibrationKey
            End Get
        End Property
        Friend ReadOnly Property BaseCaseKeyColumn As DataColumn
                Return Me.columnBaseCaseKey
            End Get
        End Property
        Friend ReadOnly Property ModelKeyColumn As DataColumn
                Return Me.columnModelKey
            End Get
        End Property
        Friend ReadOnly Property CalibrationStateColumn As DataColumn
                Return Me.columnCalibrationState
            End Get
        End Property
        Friend ReadOnly Property BaseCaseTypeColumn As DataColumn
                Return Me.columnBaseCaseType
            End Get
        End Property
        Public Default ReadOnly Property Item(ByVal index As Integer) As BaseCaseInfoRow
                Return CType(Me.Rows(index),BaseCaseInfoRow)
            End Get
        End Property
        Public Event BaseCaseInfoRowChanged As BaseCaseInfoRowChangeEventHandler
        Public Event BaseCaseInfoRowChanging As BaseCaseInfoRowChangeEventHandler
        Public Event BaseCaseInfoRowDeleted As BaseCaseInfoRowChangeEventHandler
        Public Event BaseCaseInfoRowDeleting As BaseCaseInfoRowChangeEventHandler
        Public Overloads Sub AddBaseCaseInfoRow(ByVal row As BaseCaseInfoRow)
        End Sub
        Public Overloads Function AddBaseCaseInfoRow(ByVal CalibrationKey As String, ByVal BaseCaseKey As String, ByVal ModelKey As String, ByVal CalibrationState As Short, ByVal BaseCaseType As Long) As BaseCaseInfoRow
            Dim rowBaseCaseInfoRow As BaseCaseInfoRow = CType(Me.NewRow,BaseCaseInfoRow)
            rowBaseCaseInfoRow.ItemArray = New Object() {CalibrationKey, BaseCaseKey, ModelKey, CalibrationState, BaseCaseType}
            Return rowBaseCaseInfoRow
        End Function
        Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
            Return Me.Rows.GetEnumerator
        End Function
        Public Overrides Function Clone() As DataTable
            Dim cln As BaseCaseInfoDataTable = CType(MyBase.Clone,BaseCaseInfoDataTable)
            Return cln
        End Function
        Protected Overrides Function CreateInstance() As DataTable
            Return New BaseCaseInfoDataTable
        End Function
        Friend Sub InitVars()
            Me.columnCalibrationKey = Me.Columns("CalibrationKey")
            Me.columnBaseCaseKey = Me.Columns("BaseCaseKey")
            Me.columnModelKey = Me.Columns("ModelKey")
            Me.columnCalibrationState = Me.Columns("CalibrationState")
            Me.columnBaseCaseType = Me.Columns("BaseCaseType")
        End Sub
        Private Sub InitClass()
            Me.columnCalibrationKey = New DataColumn("CalibrationKey", GetType(System.String), Nothing, System.Data.MappingType.Element)
            Me.columnBaseCaseKey = New DataColumn("BaseCaseKey", GetType(System.String), Nothing, System.Data.MappingType.Element)
            Me.columnModelKey = New DataColumn("ModelKey", GetType(System.String), Nothing, System.Data.MappingType.Element)
            Me.columnCalibrationState = New DataColumn("CalibrationState", GetType(System.Int16), Nothing, System.Data.MappingType.Element)
            Me.columnBaseCaseType = New DataColumn("BaseCaseType", GetType(System.Int64), Nothing, System.Data.MappingType.Element)
            Me.columnCalibrationKey.AllowDBNull = false
            Me.columnBaseCaseKey.AllowDBNull = false
            Me.columnModelKey.AllowDBNull = false
            Me.columnCalibrationState.AllowDBNull = false
            Me.columnBaseCaseType.AllowDBNull = false
        End Sub
        Public Function NewBaseCaseInfoRow() As BaseCaseInfoRow
            Return CType(Me.NewRow,BaseCaseInfoRow)
        End Function
        Protected Overrides Function NewRowFromBuilder(ByVal builder As DataRowBuilder) As DataRow
            Return New BaseCaseInfoRow(builder)
        End Function
        Protected Overrides Function GetRowType() As System.Type
            Return GetType(BaseCaseInfoRow)
        End Function
        Protected Overrides Sub OnRowChanged(ByVal e As DataRowChangeEventArgs)
            If (Not (Me.BaseCaseInfoRowChangedEvent) Is Nothing) Then
                RaiseEvent BaseCaseInfoRowChanged(Me, New BaseCaseInfoRowChangeEvent(CType(e.Row,BaseCaseInfoRow), e.Action))
            End If
        End Sub
        Protected Overrides Sub OnRowChanging(ByVal e As DataRowChangeEventArgs)
            If (Not (Me.BaseCaseInfoRowChangingEvent) Is Nothing) Then
                RaiseEvent BaseCaseInfoRowChanging(Me, New BaseCaseInfoRowChangeEvent(CType(e.Row,BaseCaseInfoRow), e.Action))
            End If
        End Sub
        Protected Overrides Sub OnRowDeleted(ByVal e As DataRowChangeEventArgs)
            If (Not (Me.BaseCaseInfoRowDeletedEvent) Is Nothing) Then
                RaiseEvent BaseCaseInfoRowDeleted(Me, New BaseCaseInfoRowChangeEvent(CType(e.Row,BaseCaseInfoRow), e.Action))
            End If
        End Sub
        Protected Overrides Sub OnRowDeleting(ByVal e As DataRowChangeEventArgs)
            If (Not (Me.BaseCaseInfoRowDeletingEvent) Is Nothing) Then
                RaiseEvent BaseCaseInfoRowDeleting(Me, New BaseCaseInfoRowChangeEvent(CType(e.Row,BaseCaseInfoRow), e.Action))
            End If
        End Sub
        Public Sub RemoveBaseCaseInfoRow(ByVal row As BaseCaseInfoRow)
        End Sub
    End Class

Let me know if I can include anything else for you.


Expert Comment

ID: 7170234

    <WebMethod(MessageName:="WebFoo")> _
     Public Function WebFoo( _
        ByVal sSomeKey As String _
        ) _
        As DataSet

        Dim OriginalDS As New RMXDataModel()
        Dim ResultDS As New DataSet()
        '  fill table in originalds

        '  copy to a generic dataset and return
        Return ResultDS
    End Function
    ' the user of this function will find the table in
    ' webfoo("0").Tables(0)
    ' if you want them to have the same functionality
    '  you have while using a typed dataset
    '  there are a couple things you ought to be doing
    '  I can explain them if you decide to go this way
Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.


Author Comment

ID: 7170263

Thanks for the response.

[1] I haven't tried your code yet, but how does ResultDS get populated in the code you suggest?  I just see you allocating ResultDS, but not how it gets populated by the original's BaseCaseInfo table.

[2] Yes, I'd like to return a typed dataset.  I'm convinced there's got to be some way to do that...

Expert Comment

ID: 7170312
You could create another typed dataset, simplified to the single table that you want to return.  Populate either through a query or looping through your existing table.  Then, return that newly typed dataset.

Author Comment

ID: 7170328
Wouldn't another typed dataset require another (different) XSD?

Author Comment

ID: 7170376
Okay, maybe I'm onto something: the DataTable.Copy() method:

   DataTable.Copy Method  [Visual Basic]

   Copies both the structure and data for this DataTable.

Now, maybe we can figure out how to get this added to a typed DataSet, and returned back to the WinClient correctly.  More to come...

Author Comment

ID: 7170404
Well, my WebMethod is now return=typed to return that typed DataSet, but my WinClient fails when I try to use this kind of call:

oRMXDataSet = CType(MyWebService.WebFoo(sKey), RMXDataModelLib.RMXDataModel)

Error text:

Value of type 'MagicBusWinClient.moscow.RMXDataModel' cannot be converted to 'RMXDataModelLib.RMXDataModel'.


Expert Comment

ID: 7170749
wow, that's gnarly looking...  :)

It looks like it's detecting that the typed DataSets are different and not allowing the conversion to happen.  How do you fix that?  No idea.

So, are you using the same typed dataset for your full data representation (with a bunch of tables) and the new one that your returning (with just the single table)?  I'm afraid that that might be the problem because when it's deserializing the data returned from the web service, it's expecting dataset to have the full representation.

Author Comment

ID: 7172304
Well, but that's what's funny about instantiating these new MyDataSet objects--after creating a new one, I have to remove the BaseCaseInfo table that it ALREADY HAS (which would be structure-only, of course) before I add in my POPULATED one.  That made sense when I finally saw that.  

But what that means is that both Datasets (the fully-populated one and the one with just one table populated) are identical in structure--the newer one would only have a single table populated, but the other tables are there (in stucture only).  So these 2 DataSets ought to be no different than say, two structs, one with initialized values and one without.  The fact that I have to remove a table from a just-allocated MyDataSet object tells me these things ought to be identical in their types, just not in their rowcounts.  Know what I mean?

Accepted Solution

wile_e_coyote earned 400 total points
ID: 7174217
Take a look at the DataSet.Merge (DataTable) method.  This method allows you to merge a DataTable (both the structure AND the data) into a DataSet.

Regarding question [1] in your original post, for some reason, MS does not provide a serialization mechanism for the DataTable, therefore there's no way for you to return a DataTable object from a web service.  As you've (painfully) discovered, the only way to return the DataTable is to wrap a DataSet around it.

Expert Comment

ID: 7174500
Here's an article which might be helpful.  It discusses the issues of passing typed objects between 2 web services, but it also might be appropriate for your situation.


Author Comment

ID: 7174679
Finally finding the DataTable.Copy() method got me to the point where I could get the whole table (structure and data) into the DataSet.

So, at the moment, I'm just doing that, then sending the DataSet.GetXml() string back from my Web Service.  So I got past that, though I'd really still like to exchange typed DataTables.

So, I'll take a look at your article.  Thanks.

Author Comment

ID: 7223591
Never got typed-table exchanging down, but this question has been hanging around long enough.  My finding that I needed to use .Copy(), not .Clone() was very helpful, but some of what Coyote provided was also helpful, so I'm going to send the points his way.

oops--EE is giving me some weird error when trying to accept Coyote's comments as the answer.  Will try to accept his answer later.

Author Comment

ID: 7236437
Sorry for the delay in grading this question.  I had to post a question in Community Support documenting an error that occurred every time I tried to award points.  Seems to be fixed now.


Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

In real business world data are crucial and sometimes data are shared among different information systems. Hence, an agreeable file transfer protocol need to be established.
An ASP.NET Web Form User Control is not newly introduced in ASP.NET. In fact, it was an old technology yet still playing a role to generate web content, especially when we want to use it to have a better and easy way to control part of the web conte…
Watch the video to know how one can repair corrupt Exchange OST file effortlessly and convert OST emails to MS Outlook PST file format by using Kernel for OST to PST converter tool. It can convert OST to MSG, MBOX, EML to access them. It can migrate…
To export Lotus Notes to Outlook PST or Exchange and Domino Server files to Exchange Server or PST files with ease, go for Kernel for Lotus Notes to Outlook conversion tool. Through the video, you can watch the conversion process. A common user with…
Suggested Courses

627 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question