DevForce® Classic Tech Tips
Query Strategies 



Query Strategies Level 100
DevForce Express
July 7, 2007

Through its QueryStrategy object, DevForce gives you control over both the source of the data returned in a query, and how the data obtained from that source is merged with existing data in the PersistenceManager cache.

All forms of the PersistenceManager’s GetEntity(),GetEntities(), and GetEntitiesAsync() methods permit you to specify explicitly what QueryStrategy you want used in the specified data retrieval operation.  In addition, every PersistenceManager has a DefaultQueryStrategy that is used whenever you do not explicitly specify the query strategy you want.

The QueryStrategy object has three properties: FetchStrategy, MergeStrategy, and TransactionSettings. The FetchStrategy controls where DevForce looks for the requested data: in the cache, in the datasource, or in some combination of the two.  The MergeStrategy controls how DevForce resolves conflicts between the states of objects which, although already in the cache, are also retrieved from an external source. The TransactionSettings object permits you to control the TimeOut and IsolationLevel associated with a query, and also whether and how to use the Microsoft Distributed Transaction Coordinator.

FetchStrategies

DevForce supports four different FetchStrategies: CacheOnly, CacheThenDataSource, DataSourceOnly, and DataSourceThenCache. The meaning of these is shown in Table 1.

Table 1 . FetchStrategies

Strategy Action
CacheThenDataSource Check the query cache to see if the code has previously submitted the current query or an identifiable superset of it. [1]  If so, satisfy the query from the entity cache, and skip the trip to the datasource. If the query cache contains no query matching or encompassing the current query, DevForce will go to the datasource for the requested data. Having retrieved data from the datasource into the entity cache, it will then obtain from the entity cache the final set of entity references it returns to the caller. By so doing, it will return references to new entities that exist only in the cache, as well as those that duplicate rows from the datasource.
CacheOnly Search the cache only. Ignore the data source.
DataSourceOnly Search only in the data source; return only the entities found in the data source.
DataSourceThenCache First retrieve matching entries from the datasource into the entity cache. Then return references to all matching cached entities.

Note that the first-listed FetchStrategy, CacheThenDataSource, behaves exactly like the last-listed one, DataSourceThenCache, except that a preliminary discrimination is made regarding whether to make a trip to the datasource at all.  If such a trip is made, the operation of the two strategies is identical.

Operation of FetchStrategies When Disconnected

If the client is disconnected from the data source, then the last two options, DataSourceOnly and DataSourceThenCache, will throw an InvalidOperationException. However, the first option, CacheThenDataSource, will simply return references to whatever is in the cache, regardless of whether it would go to the datasource if it could.

MergeStrategies

A MergeStrategy comes into play whenever DevForce discovers that an entity retrieved from an external source already exists in the entity cache. (The two versions are recognized as the same entity because of matching type and primary key value.)  The MergeStrategy determines how DevForce will resolve any conflict found in the two instances of the entity.

DevForce supports five different MergeStrategies: PreserveChanges, OverwriteChanges, PreserveChangesUnlessOriginalObsolete, PreserveChangesUpdateOriginal, and NotApplicable. Their meanings are shown in Table 2. 

When reviewing the table, remember that, for every cached DevForce entity, two states are maintained: Original and Current.  The Original state comprises the set of values for all properties as they existed at the time of the last retrieval from, or save to, the datasource. The Current state comprises the set of values for the object’s properties as the end user sees them. That is, the Current state values reflect any local changes that have been made since the entity was retrieved, or last saved.  When an entity is persisted, it is the values in its Current state that are saved.

Table 2 . MergeStrategies

Strategy Action when cached entity has pending changes
PreserveChanges Preserves the state of the cached entity.
OverwriteChanges

Overwrites the cached entity with data from the data source. Sets the RowState of the cached entity to Unchanged.

PreserveChangesUnless
OriginalObsolete

Preserves the values in the Current state of the cached entity, if its Original state matches the state retrieved from the datasource.

If the state as retrieved from the datasource differs from that found locally in the Original set of property values, this indicates that the entity has been changed externally by another user or process.  In this case (with this MergeStrategy), DevForce overwrites the local entity, setting the values in both its Current and Original states to match that found in the datasource.  DevForce also sets the RowState of the cached instance to Unchanged.

PreserveChangesUpdate
Original
Unconditionally preserves the values in the Current version for the cached entity; and also updates the values in its Original version to match the values in the instance retrieved from the datasource.  This has the effect of rendering the local entity savable, when it might otherwise trigger a concurrency exception.
NotApplicable This merge strategy must be used – and may only be used – with the CacheOnly fetch strategy. No merge action applies because no data is retrieved from any source outside the cache.

DevForce’s support for the PreserveChangesUnlessOriginalObsolete and PreserveChangesUpdateOriginal MergeStrategies, like its detection of concurrency violations, depends upon the designation of a ConcurrencyColumn in the source table.  To determine if the Original state of a local entity instance matches the state of the corresponding entity in the datasource, it compares the values in the designated concurrency column. 

Pre-Defined QueryStrategies

As we mentioned previously, every QueryStrategy combines a FetchStrategy with a MergeStrategy. Since there are four of the former and five of the latter, there are potentially 20 versions of QueryStrategy, even keeping the TransactionSettings constant.

Of course, the NotApplicable MergeStrategy doesn’t apply to the CacheThenDataSource, DataSourceOnly, and DataSourceThenCache FetchStrategies, so that brings us down to 17 possible combinations.  However, some of the combinations are more generally applicable and useful than others, so DevForce has identified four of the 17 possible combinations as being of particular significance, and has enshrined these four as pre-defined QueryStrategies. These pre-defined QueryStrategies combine Fetch and Merge strategies as shown in Table 3.

Table 3 . Fetch and merge strategies of the common query strategies

Query Strategy Fetch Strategy Merge Strategy
Normal CacheThenDataSource PreserveChanges
CacheOnly CacheOnly NotApplicable
DataSourceOnly DataSourceOnly OverwriteChanges
DataSourceThenCache DataSourceThenCache OverwriteChanges


Here’s a usage sample of a pre-defined QueryStrategy:

C#  
 

EntityQuery anEntityQuery =
  new EntityQuery(typeof(Employee), Employee.BirthDateEntityColumn,
  EntityQueryOp.GT, new DateTime(1900, 1, 1));

mEmployees = mPersMgr.GetEntities<Employee>(
  anEntityQuery, QueryStrategy.DataSourceThenCache);

VB:  
 

Dim anEntityQuery As EntityQuery = _
  New EntityQuery(GetType(Employee), _
  Employee.BirthDateEntityColumn, EntityQueryOp.GT, _
  New DateTime(1900, 1, 1))

mEmployees = mPersMgr.GetEntities(Of Employee)( _
  anEntityQuery, QueryStrategy.DataSourceThenCache)

Here’s how to define and use a custom QueryStrategy:

C#  
 

EntityQuery anEntityQuery =
  new EntityQuery(typeof(Employee), Employee.BirthDateEntityColumn,
  EntityQueryOp.GT, new DateTime(1900, 1, 1));
QueryStrategy aQueryStrategy =
  new QueryStrategy(FetchStrategy.DataSourceThenCache,   MergeStrategy.PreserveChangesUpdateOriginal);mPersMgr.GetEntities<Employee>(anEntityQuery,aQueryStrategy); 

mEmployees = mPersMgr.GetEntities<Employee>(
  anEntityQuery, aQueryStrategy);

VB:  
 

Dim anEntityQuery As EntityQuery = _
  New EntityQuery(GetType(Employee), _
  Employee.BirthDateEntityColumn, EntityQueryOp.GT, _
  New DateTime(1900, 1, 1))
Dim aQueryStrategy As QueryStrategy = _
  New QueryStrategy(FetchStrategy.DataSourceThenCache, _
  MergeStrategy.PreserveChangesUpdateOriginal)
mPersMgr.GetEntities(Of Employee)(anEntityQuery,aQueryStrategy) 

mEmployees = mPersMgr.GetEntities(Of Employee)( _
  anEntityQuery, aQueryStrategy)

DefaultQueryStrategy

We mentioned early in this article that the DevForce PersistenceManager has a DefaultQueryStrategy property that can be used to shape the fetch and merge behavior of queries where the QueryStrategy is not explicitly specified. The default setting for the PersistenceManager’s DefaultQueryStrategy is QueryStrategy.Normal. If you leave this setting at its default value, and in an individual query do nothing to countermand the default settings, then the FetchStrategy of CacheThenDataSource will be used in combination with the MergeStrategy of PreserveChanges.

If for some reason you wanted a PersistenceManager where the default QueryStrategy would involve a trip to the datasource, you could assign a different QueryStrategy to the PM’s DefaultQueryStrategy property. For a given query, you could still use any desired QueryStrategy by explicitly specifying a different one.

When to Use The Different QueryStrategies

For most users, most of the time, the DevForce defaults are perfect: satisfy a query from the entity cache whenever possible; and when a trip to the datasource is found necessary, resolve any conflicts that occur between incoming data and data already cache by giving the local version priority.

Your choice of a non-default strategy can be driven by a variety of things. For example, suppose your application supports online concert ticket sales. Your sales clerks need absolutely up-to-date information about what seats are available at the time they make a sale. In that use case, it will be essential to direct your query for available seats against the datasource.

In the Handling Concurrency Conflicts instructional unit, we use the MergeStrategy of PreserveChangesUpdateOriginal in our ConcurrencyHandler class to make an entity in conflict savable.  We do this only after calling the conflict to the attention of the end user who attempted the save, and having her confirm for us that, now fully informed about the state of the back-end object, she still wants to save her changes.

You can and will think of your own reasons to use different QueryStrategies. Just ask yourself, for a given data retrieval operation, whether the data in the cache is good enough, or you need absolutely current data from the source. Then ask yourself how you want to resolve conflicts between data already cached and duplicate incoming data, if such conflicts occur. DevForce gives you the flexibility to set the behavior exactly as need it. 


[1] For example, if you’re now asking for all Customers from the Eastern region, and you previously retrieved all Customers (without qualification), the all-Customers-without-qualification query will be recognized as a superset of the all-Customers-from-the-Eastern-region query, and the new query will be satisfied from the entity cache.