Solved

Return a DataTable from a WebMethod

Posted on 2002-07-22
15
788 Views
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.
0
Comment
Question by:bryker
  • 9
  • 2
  • 2
  • +2
15 Comments
 
LVL 23

Expert Comment

by:naveenkohli
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.
0
 

Author Comment

by:bryker
ID: 7170198
naveenkohli:

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:complexType>
                  <xs:sequence>
                        <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:sequence>
            </xs:complexType>
            <xs:key name="BaseCaseInfoKey" msdata:PrimaryKey="true">
                  <xs:selector xpath="." />
                  <xs:field xpath="mstns:CalibrationKey" />
            </xs:key>
      </xs:element>
   </xs:schema>

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()
            MyBase.New("BaseCaseInfo")
            Me.InitClass
        End Sub
       
        Friend Sub New(ByVal table As DataTable)
            MyBase.New(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
            Me.DisplayExpression = table.DisplayExpression
        End Sub
       
        <System.ComponentModel.Browsable(false)>  _
        Public ReadOnly Property Count As Integer
            Get
                Return Me.Rows.Count
            End Get
        End Property
       
        Friend ReadOnly Property CalibrationKeyColumn As DataColumn
            Get
                Return Me.columnCalibrationKey
            End Get
        End Property
       
        Friend ReadOnly Property BaseCaseKeyColumn As DataColumn
            Get
                Return Me.columnBaseCaseKey
            End Get
        End Property
       
        Friend ReadOnly Property ModelKeyColumn As DataColumn
            Get
                Return Me.columnModelKey
            End Get
        End Property
       
        Friend ReadOnly Property CalibrationStateColumn As DataColumn
            Get
                Return Me.columnCalibrationState
            End Get
        End Property
       
        Friend ReadOnly Property BaseCaseTypeColumn As DataColumn
            Get
                Return Me.columnBaseCaseType
            End Get
        End Property
       
        Public Default ReadOnly Property Item(ByVal index As Integer) As BaseCaseInfoRow
            Get
                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)
            Me.Rows.Add(row)
        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}
            Me.Rows.Add(rowBaseCaseInfoRow)
            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)
            cln.InitVars
            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.Columns.Add(Me.columnCalibrationKey)
            Me.columnBaseCaseKey = New DataColumn("BaseCaseKey", GetType(System.String), Nothing, System.Data.MappingType.Element)
            Me.Columns.Add(Me.columnBaseCaseKey)
            Me.columnModelKey = New DataColumn("ModelKey", GetType(System.String), Nothing, System.Data.MappingType.Element)
            Me.Columns.Add(Me.columnModelKey)
            Me.columnCalibrationState = New DataColumn("CalibrationState", GetType(System.Int16), Nothing, System.Data.MappingType.Element)
            Me.Columns.Add(Me.columnCalibrationState)
            Me.columnBaseCaseType = New DataColumn("BaseCaseType", GetType(System.Int64), Nothing, System.Data.MappingType.Element)
            Me.Columns.Add(Me.columnBaseCaseType)
            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)
            MyBase.OnRowChanged(e)
            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)
            MyBase.OnRowChanging(e)
            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)
            MyBase.OnRowDeleted(e)
            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)
            MyBase.OnRowDeleting(e)
            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)
            Me.Rows.Remove(row)
        End Sub
    End Class


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

0
 
LVL 2

Expert Comment

by:Rusk
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

        OriginalDS.Tables.Add(OriginalDS.BaseCaseInfo.Clone)
        '  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
0
 

Author Comment

by:bryker
ID: 7170263
Rusk:

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...
0
 
LVL 4

Expert Comment

by:DarthPedro
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.
0
 

Author Comment

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

Author Comment

by:bryker
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...
0
6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

 

Author Comment

by:bryker
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'.

<sigh>
0
 
LVL 4

Expert Comment

by:DarthPedro
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.
0
 

Author Comment

by:bryker
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?
0
 
LVL 4

Accepted Solution

by:
wile_e_coyote earned 100 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.
0
 
LVL 4

Expert Comment

by:wile_e_coyote
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.

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnservice/html/service07162002.asp
0
 

Author Comment

by:bryker
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.
0
 

Author Comment

by:bryker
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.
0
 

Author Comment

by:bryker
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.

Thanks.
0

Featured Post

6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

Join & Write a Comment

A basic question.. “What is the Garbage Collector?” The usual answer given back: “Garbage collector is a background thread run by the CLR for freeing up the memory space used by the objects which are no longer used by the program.” I wondered …
In my previous article (http://www.experts-exchange.com/Programming/Languages/.NET/.NET_Framework_3.x/A_4362-Serialization-in-NET-1.html) we saw the basics of serialization and how types/objects can be serialized to Binary format. In this blog we wi…
Access reports are powerful and flexible. Learn how to create a query and then a grouped report using the wizard. Modify the report design after the wizard is done to make it look better. There will be another video to explain how to put the final p…
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…

757 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

Need Help in Real-Time?

Connect with top rated Experts

23 Experts available now in Live!

Get 1:1 Help Now