Microsoft KB Archive/183315
Article ID: 183315
Article Last Modified on 3/14/2005
- Microsoft Remote Data Services 1.5
- Remote Data Service for ADO 2.0
- Microsoft Remote Data Services 2.1
- Remote Data Service for ADO 2.5
- Remote Data Service for ADO 2.6
- Microsoft Remote Data Services 2.0
This article was previously published under Q183315
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).
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:
The business object is not registered safe for launching or the ProgID that is registered is misspelled. A registry key whose name matches the ProgID of the business object, "VBCustBusObj.cbo", must be added to the following location:
\HKEY_LOCAL_MACHINE \SYSTEM \CurrentControlSet \Services \W3SVC \Parameters \ADCLaunch
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:
- If you use ODBC to expose data to RDS, the data source name (DSN) must be a System DSN or a File DSN, not a User DSN.
- The connection or query information is not correct and will not work whether you are using RDS to expose an ADO recordset or not.
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 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:
183609 FILE: Rdsvb.exe Demonstrates How to Use RDS with Visual Basic
To see another example of conflict resolution, please see the following article in the Microsoft Knowledge Base:
177720 FILE: Rdsensub.exe with RDS Conflict Resolution Sample
Keywords: kbhowto kbdatabase KB183315