| 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:
- It
is flagged with the [AllowRpc] attribute;
- It
embodies an appropriate method signature; and
- (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.
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:
- Import the data into a temporary, empty
PM.
- 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.]
|