HOWTO: Write and Validate a Custom Business Object with RDS

ID: Q183315


The information in this article applies to:


SUMMARY

This article describes how to build, debug, and validate a custom business object for use with the Remote Data Service (RDS). The sample code is written in Visual Basic, however, the techniques demonstrated are valid regardless of the actual language you use to implement the business object (if the language can generate an ActiveX DLL).


MORE INFORMATION

The RDSServer.DataFactory business object provided by RDS has four methods. However, two are typically found in any given business object: the Query method, which returns a recordset, and the SubmitChanges method, which processes and attempts to post any changes made to that recordset. Your business object will likely need the equivalent of these two methods. In addition, two others will be handy for validating both the functionality and installation of your custom business object.

Method 1 - SumInt()


SumInt() tests if the Business Object can be created.

This is a simple method that returns the sum of two integers you provide as arguments. You would use this method to confirm that the business object can be created correctly from either the local or the remote client. If it fails, then either the business object is not correctly registered through Regsvr32.exe, or additional "safe for..." registry entries may be required on the server or the client.


   Public Function SumInt(FirstInt As Integer, _
    SecondInt As Integer) As Integer
      SumInt = FirstInt + SecondInt
   End Function 


Possible causes for failure could include the following:

Method 2 - ReturnRSValue()


ReturnRSValue() tests if the Recordset can be opened.

This method verifies that the connection and query information you are providing to the business object is correct. It will open but not return an ADO Recordset. It does return an array of variants for the first record in the recordset obtained by the GetRows method. If this is successful, then you know the Recordset you are requesting can be created on the server, which helps isolate any difficulty from an inability to receive it on the client.

Following is some sample code for the ReturnRSValue method:


   Public Function ReturnRSValue(strConnect As String, _
    strQuery As String) As Variant

   Dim adoCon1 As New ADODB.Connection
   Dim adoRs1  As New ADODB.Recordset
   Dim x As Variant    ' Variant array to send to the Client

  ' Open a connection to the Database.
   adoCon1.Open strConnect

  ' Open an ADO Recordset.
  adoRs1.Open strQuery, adoCon1, adOpenStatic, adLockBatchOptimistic


 ' Fill the Variant array using GetRows.
  x = adoRs1.GetRows(adGetRowsRest, adBookmarkFirst)

 ' Return the Variant array to the Client.
   ReturnRSValue = x

   End Function<BR/> 

Possible causes of failure could include the following:

Method 3 - ReturnRS()



ReturnRS() tests if a Recordset can be returned.

This method returns the recordset that ReturnRSValue() opened. This is actually an archetype method for a business object, and equivalent to the RDSServer.DataFactory's Query() method. However, because you are implementing this method yourself, you can take advantage of the full ADO Object model, such as using the Command object to open a parameterized stored procedure. The DataFactory would not be able to do this as it just creates a standalone Recordset object. However, you should open the exact same kind of recordset in ReturnRSValue() as ReturnRS().

Following is some sample code for this method:


   Public Function ReturnRS(strConnect As String, _
    strQuery As String) As ADODB.Recordset
       Dim adoCon1 As New ADODB.Connection
       Dim adoRs1 As New ADODB.Recordset

       ' Set the CursorLocation to adUseClient to return an
       ' ADORecordset to an RDS DataControl.
       adoCon1.CursorLocation = adUseClient

       ' Open ADO Connection with passed in connect string.
       adoCon1.Open strConnect

       ' Open ADO Recordset with passed in SQL string.
       adoRs1.Open strQuery, adoCon1, adOpenKeyset, adLockBatchOptimistic

       ' Return ADO Recordset object to Client.
       ' (Returns the actual recordset not just a pointer to it).
       Set ReturnRS = adoRs1

       ' Can not close the ADO Recordset object here,
       ' but it can be disassociated.
       Set adoRs1.ActiveConnection = Nothing

       ' Close ADO Connection object.
       adoCon1.Close

   End Function 


Method 4 - EnhancedSubmit()


EnhancedSubmit() sends changes back to the DataStore.

This method accepts a recordset and uses it to re-open a connection to the underlying DataStore and submit, in batch, any of the changes you may have made to that recordset. For any records that were not posted to the DataStore, it provides details indicating why the failure may have occurred. It also returns a second recordset that contains just the records that failed to be posted to the DataStore.

This is actually an archetype method for a business object, and is a superset of the functionality found in the SubmitChanges() method of the RDS.DataControl and RDSServer.DataFactory.

Note that the method checks for the occurrence of error 3617, an undocumented RDS error. This may, depending upon the development environment you use, come back as either 0x80040E21 or -2147217887.

Following is some sample code for this method:


Public Function EnhancedSubmit(strConnect As String, _
                                  rs As ADODB.Recordset, _
                                  nPosted As Integer, _
                                  vStatus() As Variant)
       Dim i As Integer
       Dim s As String
       dim nConflict as integer
       Dim r As New ADODB.Recordset

       On Error GoTo ErrCond

       s = ""
       nPosted = 0
       nConflict = 0

       ' Re-establish connection, submit changes &
       ' count # affected records
       r.Open rs, strConnect
       r.UpdateBatch adAffectAll  ' Send in modifications
       r.Filter = adFilterAffectedRecords '
       nPosted = rs.RecordCount

       ' Determine if there are any conflicts and what type...
       ReDim vStatus(r.RecordCount)
       i = 0
       r.MoveFirst
       While r.EOF = False
           vStatus(i) = r.Fields(0).value & ":  "
           CheckStatus r.status, vStatus(i)
           ' Only report back errors, not accepted or unmodified recs(!)
           If (r.status <> adRecOK) And (r.status <> adRecUnmodified) Then
               If Len(s) > 0 Then s = s & vbCrLf
               s = s & vStatus(i)
               nConflict = nConflict + 1
           End If
           r.MoveNext
           i = i + 1
       Wend

       r.Close
       Set r = Nothing

       ' Set message to return to client
       If Len(s) = 0 Then
           s = s & "Success!  " & Str(nPosted) & " Records Posted."
       Else
           s = s & vbCrLf & _
               "Attempted to Post " & Str(nPosted) & " Records, " & _
               Str(nConflict) & " Conflicts Encountered(!)"
       End If

       EnhancedSubmit = s

       Exit Function

   ErrCond:

     Select Case Err.Number
            Case -2147217887 ' Raised when ALL records conflict
                s = "Conflicts Occurred in UpdateBatch!"
                Resume Next
            Case Else
                Test4_EnhancedSubmit = "FAILURE " & Str(Err.Number) & _
                                       "--" & Err.Description
        End Select
    End Function 


The CheckStatus subroutine mentioned in the preceding code above is provided in the RDS series of samples listed in the references section and builds a string based on the flags that are set in the Recordset.Status property. You can use this to see why a given record failed. A variation on this conflict resolution code is provided in the RDS* series of samples, as well as the RDSENSUB sample also listed below.

General Diagnosis and Debugging Tips


Using a Local Client


When you write your client, you will not be able to step into the business object if RDS is used over HTTP to invoke it. You should consider the advantages of writing a local client which just instantiates the business object independent of RDS. This is easily done for Visual Basic for Applications, C++, or Java; however, if you are using VBScript, you should consider writing a Visual Basic Local Client to get the same effect.

This lets you validate the functionality of your business object, and debug any issues that might be there. Especially for Visual Basic-based business objects, as the compilation doesn't always catch syntax errors that the more rigorous C++ or Java Compilers would prevent.

Whatever methods the remote client using RDS invokes, you should also invoke with your local client to ensure the validation of the code within the business object.

However, there will be some differences in behavior with a local client. For example, the recordset MarshalOptions property will have no effect in a local client. Consider this code:


   rs.MarshalOptions = adMarshalModifiedOnly 

This tells RDS to submit records only to the business object (custom or default) that are changed or added (and of course indicate any deletions that may have occurred). With your local client, as you are bypassing RDS, there is no marshalling; therefore, this property has no affect. The entire recordset is passed to the business object, not just the modified records.

The Joy of ClsidView


The CLSIDView utility is an ideal mechanism for verifying the registration of your RDS Server or Client components.

The CLSIDView utility is available for download from the Microsoft Software Library. For download information, please see the following article in the Microsoft Knowledge Base:

Q176562 FILE: Clsidvw.exe: OLE/Createable Objects Registry Diagnostic

ClSIDViews can also be used to manage the registration of your business object. Once you have registered it with Regsvr32.dll, in the Find combo box, type the name of your business object; that is, Vbcbo.dll. Then click REFRESH. ClsidView will find any and all instances of that DLL listed in the registry, and allow you to register that DLL as one of the three following options:

You can, of course, add the necessary registry keys by hand, or by using a registry file such as shown below:


REGEDIT4

   [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters\ 
   ADCLaunch\[YourProgIDGoesHere]] 

   ;RDSEnSub.EnhancedSubmit safe for scripting and initialization
   [HKEY_CLASSES_ROOT\CLSID\{[Your CLSID Goes Here]}\Implemented
   Categories\{7DD95801-9882-11CF-9FA9-00AA006C42C4}]
   [HKEY_CLASSES_ROOT\CLSID\{[Your CLSID Goes Here]}\Implemented
   Categories\{7DD95802-9882-11CF-9FA9-00AA006C42C4}] 


REFERENCES

The techniques in this article are demonstrated by the RDS* Series of Samples. They demonstrate the three techniques for returning data in different languages/development environments, but also include a comprehensive business object, as well as a local and remote client for testing and validating that object. The current list of samples available follows:


      QNumber                           Title
      -------  ----------------------------------------------------------- 
Q183609 FILE: Rdsvb.exe Demonstrates How to Use RDS with Visual Basic

The CLSIDView sample mentioned above may be obtained via the following article in the Microsoft Knowledge Base:

Q176562 FILE: Clsidvw.exe OLE/Createable Objects Registry Diagnostic


To see another example of conflict resolution, please see the following article in the Microsoft Knowledge Base:

Q177720 FILE: Rdsensub.exe with RDS Conflict Resolution Sample

Additional query words: kbdse kbcrossref


Keywords          : kbRDS150 kbRDS200 
Version           : WINDOWS:1.5
Platform          : WINDOWS 
Issue type        : kbhowto 

Last Reviewed: January 30, 1999