DevForce® Classic Tech Tips
Extract Maximum Data Retrieval Performance



Extract Maximum Data Retrieval Performance With a Remote Service Method Level 300
DevForce Enterprise with BOS
Oct 31, 2007
DevForce span queries provide an easy, efficient way to retrieve related entities of different types in a single trip to the Business Object Server. Span queries submitted asynchronously can permit the user interface to continue with full responsiveness while that data is being retrieved.

But what if you need to get a big chunk of data that includes not only different entity types, but also unrelated entity types? Let’s say, for example, that you need to retrieve the Manhattan phone book along with a hundred thousand restaurant menus and some cruise ship itineraries. Maybe you also need to do this without holding up the end user on the client side of the application.

For this, a Remote Service Method is your power tool of choice. Like the span query, it can be launched synchronously or asynchronously, as desired.

Here’s an example of a method designed to be deployed server-side and invoked remotely from a DevForce client:

C#

    [AllowRpc]
    public static Object GetAppData(IPrincipal pPrincipal,
        PersistenceManager aPersistenceManager, params Object[] pArgs) {
      RdbQuery anRdbQuery = new RdbQuery(typeof(Employee));
   
   anRdbQuery.AddSpan(EntityRelations.Employee_Order,
        EntityRelations.Order_OrderDetail,
        EntityRelations.Product_OrderDetail,
        EntityRelations.Supplier_Product);
      anRdbQuery.AddSpan(EntityRelations.Employee_Order,
        EntityRelations.Customer_Order);
      anRdbQuery.AddSpan(EntityRelations.Employee_Order,
        EntityRelations.Shipper_Order);
      aPersistenceManager.GetEntities<Employee>(anRdbQuery); 

      RdbQuery anotherRdbQuery = new RdbQuery(typeof(User));
      anotherRdbQuery.AddSpan(EntityRelations.User_UserRole,
        EntityRelations.Role_UserRole);
      aPersistenceManager.GetEntities<User>(anotherRdbQuery); 

      RdbQuery stillAnotherRdbQuery = new RdbQuery(typeof(Audit));
      aPersistenceManager.GetEntities<Audit>(stillAnotherRdbQuery); 

      return new EntityDataSetSurrogate(aPersistenceManager.DataSet);
    }

 

VB

 

   <AllowRpc> _
   Public Shared Function GetAppData( _
        ByVal pPrincipal As IPrincipal, _
        ByVal aPersistenceManager As PersistenceManager, _
        ParamArray ByVal pArgs As Object()) As Object
     Dim anRdbQuery As RdbQuery = New RdbQuery(GetType(Employee))
     anRdbQuery.AddSpan(EntityRelations.Employee_Order, _
        EntityRelations.Order_OrderDetail, EntityRelations.Product_OrderDetail, _
        EntityRelations.Supplier_Product)
     anRdbQuery.AddSpan(EntityRelations.Employee_Order, _
        EntityRelations.Customer_Order)
     anRdbQuery.AddSpan(EntityRelations.Employee_Order, _
        EntityRelations.Shipper_Order)
     aPersistenceManager.GetEntities(Of Employee)(anRdbQuery) 

     Dim anotherRdbQuery As RdbQuery = New RdbQuery(GetType(User))
     anotherRdbQuery.AddSpan(EntityRelations.User_UserRole, _
        EntityRelations.Role_UserRole)
     aPersistenceManager.GetEntities(Of User)(anotherRdbQuery) 

     Dim stillAnotherRdbQuery As RdbQuery = New RdbQuery(GetType(Audit))
     aPersistenceManager.GetEntities(Of Audit)(stillAnotherRdbQuery) 

     Return New EntityDataSetSurrogate(aPersistenceManager.DataSet)
   End Function

 

GetAppData() has three characteristics that fit it for use in a Remote Service Method: 

  1. It is flagged with the [AllowRpc] attribute;
  2. It embodies an appropriate method signature; and
  3. (You can’t see this part!) It’s deployed in an assembly that lives on the application server machine.

What does GetAppData() do? It simply executes a bunch of GetEntities() calls to fill a server-side PersistenceManager; and then returns the entity contents of that PersistenceManager.  In the example above, two separate and unrelated span queries are executed, as well as a garden-variety, single-entity-type query. Instances of eleven different entity types are collected in the server-side PM before it is returned. 

We do not return the entire PersistenceManager, but rather the DataSet at its heart. In fact, we don’t actually return the DataSet, either, but rather a slightly transformed version of it – the EntityDataSetSurrogate -- that serializes better.[1] 

The only detail that remains is learning how to use the Remote Service Method.  Here’s a client-side call to the method shown above: 

C#

    public static void LoadCache(PersistenceManager pPersistenceManager) { 

      // Invoke the remote service method to get an EntityDataSetSurrogate (see comments
      // on the GetAppData() method).
      EntityDataSetSurrogate anEntityDataSetSurrogate =
        (EntityDataSetSurrogate)pPersistenceManager.InvokeServerMethod(
        new ServerRpcPersistenceDelegate(Model.RemoteServiceMethod.GetAppData));
      // Convert surrogate to a DataSet
      DataSet aDataSet = anEntityDataSetSurrogate.ToEntityDataSet(); 

      // Since the DataSet.Merge() method doesn't support merge strategies,
      // we'll copy the returned data into an empty PM, then pull it from there
      // into our main PM using the MergeStrategy of our choice. 

      // Instantiate the temporary PM using the credentials of the main PM
      PersistenceManager tempPersistenceManager =
        new PersistenceManager(pPersistenceManager);
      tempPersistenceManager.DataSet.Merge(aDataSet);
      // Using RestoreEntitySet() is the easiest way to get the entire contents of
      // the temporary PM into the main PM. We'll create a custom RestoreStrategy
      // so we can specify the MergeStrategy to be used.
      RestoreStrategy aRestoreStrategy =
        new RestoreStrategy(false, false, MergeStrategy.PreserveChanges); 

      // We'll use the overload of RestoreEntitySet that takes
      // an EntitySet directly as input using the GetEntitySet()
      // method on the temporary PM to get one.
      pPersistenceManager.RestoreEntitySet(
        tempPersistenceManager.GetEntitySet(), aRestoreStrategy);
    }

 

VB

 

   Public Shared Sub LoadCache(ByVal pPersistenceManager As PersistenceManager) 

     ' Invoke remote service method to get an EntityDataSetSurrogate (see comments
     ' on the GetAppData() method).
     Dim anEntityDataSetSurrogate As EntityDataSetSurrogate = _
        CType(pPersistenceManager.InvokeServerMethod( _
        New ServerRpcPersistenceDelegate( _
        AddressOf Model.RemoteServiceMethod.GetAppData)), _
        EntityDataSetSurrogate)
     ' Convert surrogate to a DataSet
     Dim aDataSet As DataSet = anEntityDataSetSurrogate.ToEntityDataSet() 

     ' Since the DataSet.Merge() method doesn't support merge strategies,
     ' we'll copy the returned data into an empty PM, then pull it from there
     ' into our main PM using the MergeStrategy of our choice. 

     ' Instantiate the temporary PM using the credentials of the main PM
     Dim tempPersistenceManager As PersistenceManager = _
        New PersistenceManager(pPersistenceManager)
     tempPersistenceManager.DataSet.Merge(aDataSet) 

     ' Using RestoreEntitySet() is the easiest way to get the entire contents of
     ' the temporary PM into the main PM. We'll create a custom RestoreStrategy
     ' so we can specify the MergeStrategy to be used.
     Dim aRestoreStrategy As RestoreStrategy = _
        New RestoreStrategy(False, False, MergeStrategy.PreserveChanges) 

     ' We'll use the overload of RestoreEntitySet that takes
     ' an EntitySet directly as input using the GetEntitySet()
     ' method on the temporary PM to get one.
     pPersistenceManager.RestoreEntitySet( _
        tempPersistenceManager.GetEntitySet(), aRestoreStrategy)
   End Sub

The remote method is invoked using the InvokeServerMethod() method of a client-side PersistenceManager. There are several overloads of InvokeServerMethod available: we’ve chosen the only (synchronous) one whose delegate gets a PersistenceManager passed into it as an parameter. You already seen why we need – and how we use – that server-side PersistenceManager.

The call to InvokeServerMethod identifies GetAppData() as the method to execute on the server by passing its name to the constructor of a ServerRpcPersistenceDelegate. An instance of the latter is a required parameter of the InvokeServerMethod() overload we’ve chosen.

The InvokeServerMethod() call returns an Object. In this case, we know that object is an EntityDataSetSurrogate, so we cast it to that type. We then call the EntityDataSetSurrogate’s ToEntityDataSet() method to deliver the surrogate’s contents as an ADO.NET DataSet.

Now, we’re getting close, but we still need to integrate the retrieved data with possible duplicate data already in our main PersistenceManager’s cache. We’ll do that in a two-step process: 

  1. Import the data into a temporary, empty PM.
  2. Import the data from the temporary PM into our main PM. 

We use DataSet.Merge() on the temporary PM to accomplish step 1. Then we call RestoreEntitySet() on the target PM to accomplish step 2.  

Let’s look in a little more detail at step 2. The PM has several overloads of the RestoreEntitySet() method. One takes an EntitySet parameter; and we can get an EntitySet from the temporary PM using its GetEntitySet() method.

When importing the retrieved data into the main PM, we likely want to apply a specific MergeStrategy: let’s say we want to use the PreserveChanges strategy. But RestoreEntitySet() takes a RestoreStrategy parameter rather than a MergeStrategy. So we instantiate a new RestoreStrategy(), assign to it the MergeStrategy we want used, and then pass that RestoreStrategy as the second parameter of the RestoreEntitySet() call. 

Using the Retrieved Data

When you need a list of entities in your DevForce app – for populating a grid or form, or for some other type of processing, you typically invoke the GetEntities() method of a PersistenceManager to get that list. GetEntities() may or may not retrieve data from the datasource -- it only does so when it can’t be sure it already has the requested data in the cache -- but it always returns a set of references to objects in the local cache (assuming that there were some that met the supplied specifications).

The point of your Remote Service Method Call is to fill the cache with needed data so that subsequent GetEntities() calls can deliver your desired set of object references without first having to go out to the datasource to get the data. But the PersistenceManager still needs a bit more education to make that happen.

When data retrieval from the datasource occurs in response to a GetEntities(), the PersistenceManager remembers the query that forced it to make a trip out to the datasource. When it sees that same query again, it concludes that it has the necessary objects to satisfy it in the cache already, and goes automatically into CacheOnly mode, collecting references to objects in the cache that meet the query’s specifications.

When you retrieve data by means of a Remote Service Method call, the specific queries or other mechanisms that were used to gather the data are unknown to the client-side PersistenceManager. Therefore, to realize the benefit of the data retrieval you’ve done, you must subsequently specify the CacheOnly QueryStrategy when invoking GetEntities() to get references to objects that you know were retrieved in the remote operation.

Alternatively, you might choose to “stuff” the client-side PersistenceManager’s query cache directly: DevForce provides the means for you to do so. In the case of the Manhattan phone book example we posed at the beginning of this article, you retrieved instances of PhoneBookEntry, RestaurantMenu, and CruiseShipItinerary. So you could stuff the PM’s query cache with instances of three GetEntities() calls – one for each of the types. Then when the PM saw requests for these entity types, it would check it’s query cache, find the queries you put here, conclude that it already had the needed objects, and satisfy the new request entirely from the cache.

Conclusion 

For most data retrieval operations, most of you will find DevForce’s standard, well-optimized GetEntities() retrieval behaviors to be entirely satisfactory. But if and when you need a maximally efficient, single-pass data retrieval operation involving unrelated entities, the Remote Service Method call fills the bill.

As we mentioned, your Remote Service Method calls can be made synchronously or asynchronously. Our example illustrated the synchronous call; the asynchronous version can be implemented through the PersistenceManager’s InvokeServerMethodAsync() method. If indeed you do need to load the Manhattan phone book and a boatload of other entities, launch the asynchronous version, distract your user by giving her something else useful to do in your application meanwhile, and then sit back and enjoy the glowing comments about your app’s snappy performance!

[A new 300-series instructional unit, “Remote Service Method ”includes the solution from which code samples in this Tech Tip were taken. This instructional unit will be available beginning with the November 2007 release of DevForce.]


[1] DataSets have two serialization-related problems: first, their performance degrades approximately as the square of their size; second, they are a little too helpful with transforming DateTime values as data moves across time zones, resulting in unpleasant surprises such as important dates getting shifted by a day. The DevForce EntitySetSurrogate successfully addresses both of these problems.