| |
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.
|
|
|
|
|
|