Oracle® Application Development Framework Developer's Guide For Forms/4GL Developers 10g (10.1.3.1.0) Part Number B25947-01 |
|
|
View PDF |
This section describes a number of interesting view object concepts and features that have not been discussed in previous chapters.
The default maximum fetch size of a view object is minus one (-1), which indicates that there is no artificial limit to the number of rows that can be fetched. Keep in mind that by default, rows are fetched as needed, so this default does not imply a view object will necessary fetch all the rows. It simply means that if you attempt to iterate through all the rows in the query result, you will get them all.
However, you might want to put an upper bound on the maximum number of rows that a view object will retrieve. If you write a query containing an ORDER BY
clause and only want to return the first N
rows to display the "Top-N" entries in a page, you can call the setMaxFetchSize()
method on your view object to set the maximum fetch size to N
. The view object will stop fetching rows when it hits the maximum fetch size. Often you will combine this technique with specifying a Query Optimizer Hint of FIRST_ROWS
on the Tuning panel of the View Object Editor. This gives a hint to the database that you want to retrieve the first rows as quickly as possible, rather than trying to optimize the retrieval of all rows.
When multiple instances of entity-based view objects in an application module are based on the same underlying entity object, a new row created in one of them can be automatically added (without having to re-query) to the row sets of the others to keep your user interface consistent or simply to consistently reflect new rows in different application pages for a pending transaction. Consider the SRDemo application's SRList
page that displays an end-user's list of service requests. If the end-user goes to create a new service request, this task is performed through a different view object and handled by a custom application module method. Using this view object new row consistency feature, the newly created service request automatically appears in the end-user's list of open service requests on the SRList
page without having to re-query the database.
For historical reasons, this capability is known as the view link consistency feature because in prior releases of Oracle ADF the addition of new rows to other relevant row sets only was supported for detail view object instances in a view link based on an association. Now this view link consistency feature works for any view objects for which it is enabled, regardless of whether they are involved in a view link or not.
Consider two entity-based view objects ServiceRequestSummary
and ServiceRequests
both based on the same underlying ServiceRequest
entity object. When a new row is created in a row set for one of these view objects (like ServiceRequests
) and the row's primary key is set, any of the other row sets for view objects based on the same ServiceRequest
entity object (like ServiceRequestSummary
) receive an event indicating a new row has been created. If their view link consistency flag is enabled, then a copy of the new row is inserted into their row set as well.
You can control the default setting for the view link consistency feature using the jbo.viewlink.consistent
configuration parameter. The default setting for this parameter is the word "DEFAULT
" which has the following meaning. If your view object has:
A single entity usage, view link consistency is enabled
Multiple entity usages, and:
If all secondary entity usages are marked as contributing reference information, then view link consistency is enabled
If any secondary entity usage marked as not being a reference view link consistency is disabled.
You can globally disable this feature by setting the jbo.viewlink.consistent
to the value false
in your configuration. Conversely, you could globally enable this feature by setting jbo.viewlink.consistent
to the value true
, but Oracle does not recommend doing this. Doing so would force view link consistency to be set on for view objects with secondary entity usages that are not marked as a reference which presently do not support the view link consistency feature well.
To set the feature programmatically, use the setAssociationConsistent()
API on any RowSet
. When you call this method on a view object, it affects its default row set.
If a view object has view link consistency enabled, any new row created by another view object based on the same entity object is added to its row set. By default the mechanism adds new rows in an unqualified way. If your view object has a design-time WHERE
clause that queries only a certain subset of rows, you can apply a RowMatch
object to your view object to perform the same filtering in-memory. The filtering expression of the RowMatch
object you specify prevents new rows from being added that wouldn't make sense to appear in that view object.
For example, the ServiceRequestsByStatus
view object in the SRDemo application includes a design time WHERE
clause like this:
WHERE /* ... */ AND STATUS LIKE NVL(:StatusCode,'%')
Its custom Java class overrides the create()
method as shown in Example 27-1 to force view link consistency to be enabled. It also applies a RowMatch
object whose filtering expression matches rows whose Status
attribute matches the value of the :StatusCode
named bind variable (or matches any row if :StatusCode
= '%
'). This RowMatch
filter is used by the view link consistency mechanism to qualify the row that is a candidate to add to the row set. If the row qualifies by the RowMatch
, it is added. Otherwise, it is not.
Example 27-1 Providing a Custom RowMatch to Control Which New Rows are Added
// In ServiceRequestsByStatusImpl.java protected void create() { super.create(); setAssociationConsistent(true); setRowMatch(new RowMatch("Status = :StatusCode or :StatusCode = '%'")); }
See Section 27.5.4, "Performing In-Memory Filtering with RowMatch" for more information on creating and using a RowMatch
object.
Note: If theRowMatch facility does not provide enough control, you can override the view object's rowQualifies() method to implement a custom filtering solution. Your code can determine whether a new row qualifies to be added by the view link consistency mechanism or not. |
If you call setWhereClause()
on a view object to set a dynamic where clause, the view link consistency feature is disabled on that view object. If you have provided an appropriate custom RowMatch
object to qualify new rows for adding to the row set, you can call setAssociationConsistent(true)
after setWhereClause()
to re-enable view link consistency.
View objects support two different styles of master-detail coordination:
View link instances for active data model master/detail coordination
View link accessor attributes for programmatically accessing detail row sets on demand
When you add a view link instance to your application module's data model, you connect two specific view object instances and indicate that you want active master/detail coordination between the two. At runtime the view link instance in the data model facilitates the eventing that enables this coordination. Whenever the current row is changed on the master view object instance, an event causes the detail view object to be refreshed by automatically invoking executeQuery()
with a new set of bind parameters for the new current row in the master view object.
A key feature of this active data model master/detail is that the master and detail view object instances are stable objects to which client user interfaces can establish bindings. When the current row changes in the master — instead of producing a new detail view object instance — the existing detail view object instance updates its default row set to contain a the set of rows related to the new current master row. In addition, the user interface binding objects receive events that allow the display to update to show the detail view object's refreshed row set.
Another key feature that is exclusive to active data model master/detail is that a detail view object instance can have multiple master view object instances. For example, an ExpertiseAreas
view object instance may be a detail of both a Products
and a Technicians
view object instances. Whenever the current row in either the Products
or Technicians
view object instance changes, the default row set of the detail ExpertiseAreas
view object instance is refreshed to include the row of expertise area information for the current technician and the current product. See Section 27.1.6, "Setting Up a Data Model with Multiple Masters" for details on setting up a detail view object instance with multiple-masters.
When you need to programmatically access the detail row set related to a view object row by virtue of a view link, you can use the view link accessor attribute. You control the name of the view link accessor attribute on the View Link Properties panel of the View Link Editor. Assuming you've named your accessor attribute AccessorAttrName
, you can access the detail row set using the generic getAttribute()
API like:
RowSet detail = (RowSet)currentRow.getAttribute("AccessorAttrName");
If you've generated a custom view row class for the master view object and exposed the getter method for the view link accessor attribute on the client view row interface, you can write strongly-typed code to access the detail row set like this:
RowSet detail = (RowSet)currentRow.getAccessorAttrName();
Unlike the active data model master/detail, programmatic access of view link accessor attributes does not require a detail view object instance in the application module's data model. Each time you invoke the view link accessor attribute, it returns a RowSet
containing the set of detail rows related to the master row on which you invoke it.
Using the view link accessor attribute, the detail data rows are stable. As long as the attribute value(s) involved in the view link definition in the master row remain unchanged, the detail data rows will not change. Changing of the current row in the master does not affect the detail row set which is "attached" to a given master row. For this reason, in addition to being useful for general programmatic access of detail rows, view link accessor attributes are appropriate for UI object like the tree control, where data for each master node in a tree needs to retain its distinct set of detail rows.
When you combine the use of active data model master/detail with programmatic access of detail row sets using view link accessor, it is even more important to understand that they are distinct mechanisms. For example, imagine that you have:
Defined ServiceRequests
and ServiceHistories
view objects
Defined a view link between them, naming the view link accessor HistoriesForRequest
Added instances of them to an application module's data model named master
(of type ServiceRequests
) and detail
(of type ServiceHistories
) coordinated actively by a view link instance.
If you find a service request in the master
view object instance, the detail
view object instance updates as expected to show the corresponding service request histories. At this point, if you invoke a custom method that programmatically accesses the HistoriesForRequest
view link accessor attribute of the current ServiceRequests
row, you get a RowSet
containing the set of ServiceHistory
rows. You might reasonably expect this programmatically access RowSet
to have come from the detail
view object instance in the data model, but this is not the case.
The RowSet
returned by a view link accessor always originates from an internally created view object instance, not one you that added to the data model. This internal view object instance is created as needed and added with a system-defined name to the root application module.
The principal reason a distinct, internally-created view object instance is used is to guarantee that it remains unaffected by developer-related changes to their own view objects instances in the data model. For example, if the view row were to use the detail view object in the data model for view link accessor RowSet
, the resulting row set could be inadvertently affected when the developer dynamically:
Adds a WHERE
clause with new named bind parameters
If such a view object instance were used for the view link accessor result, unexpected results or an error could ensue because the dynamically-added WHERE
clause bind parameter values have not been supplied for the view link accessor's RowSet
: they were only supplied for the default row set of the detail view object instance in the data model.
Adds an additional master view object instance for the detail view object instance in the data model.
In this scenario, the semantics of the accessor would be changed. Instead of the accessor returning ServiceHistory
rows for the current ServiceRequest
row, it could all of a sudden start returning only the ServiceHistory
rows for the current ServiceRequest
that were created by a current technician, for example.
Removes the detail view object instance or its containing application module instance.
In this scenario, all rows in the programmatically-accessed detail RowSet would become invalid.
Furthermore, Oracle ADF needs to distinguish between the active data model master/detail and view link accessor row sets for certain operations. For example, when you create a new row in a detail view object, the framework automatically populates the attributes involved in the view link with corresponding values of the master. In the active data model master/detail case, it gets these values from the current row(s) of the possibly multiple master view object instances in the data model. In the case of creating a new row in a RowSet
returned by a view link accessor, it populates these values from the master row on which the accessor was called.
To present and scroll through data a page at a time, you can configure a view object to manage for you an appropriately-sized range of rows. The range facility allows a client to easily display and update a subset of the rows in a row set, as well as easily scroll to subsequent pages N
rows as a time. You call setRangeSize()
to define how many rows of data should appear on each page. The default range size is one (1
) row. A range size of minus one (-1
) indicates the range should include all rows in the row set.
Note: When using the ADF Model layer's declarative data binding, the iterator binding in the page definition has aRangeSize property. At runtime, the iterator binding invokes the setRangeSize() method on its corresponding row set iterator, passing the value of this RangeSize property. The ADF design time by default sets this RangeSize property to 10 rows for most iterator bindings. An exception is the range size specified for a List binding to supply the set of valid values for a UI component like a dropdown list. In this case, the default range size is minus one (-1 ) to allow the range to include all rows in the row set. |
When you set a range size greater than one, you control the row set paging behavior using the iterator mode. The two iterator mode flags you can pass to the setIterMode()
method are:
RowIterator.ITER_MODE_LAST_PAGE_PARTIAL
In this mode, the last page of rows may contain fewer rows than the range size. For example, if you set the range size to 10 and your row set contains 23 rows, the third page of rows will contain only three rows. This is the style that works best for web applications.
RowIterator.ITER_MODE_LAST_PAGE_FULL
In this mode, the last page of rows is kept full, possibly including rows at the top of the page that had appeared at the bottom of the previous page. For example, if you set the range size to 10 and your row set contains 23 rows, the third page of rows will contain 10 rows, the first seven of which appeared as the last seven rows of page two. This is the style that works best for desktop-fidelity applications using Swing.
As a general rule, for highest performance, Oracle recommends building your application in a way that avoids giving the end-user the opportunity to scroll through very large query results. To enforce this recommendation, call the getEstimatedRowCount()
method on a view object to determine how many rows would be returned the user's query before actually executing the query and allowing the user to proceed. If the estimated row count is unreasonably large, your application can demand that the end-user provide additional search criteria.
However, when you must work with very large result sets, you can use the view object's access mode called "range paging" to improve performance. The feature allows your applications to page back and forth through data, a range of rows at a time, in a way that is more efficient for large data sets than the default "scrollable" access mode.
The Oracle database supports a feature called a "Top-N" query to efficiently return the first N
ordered rows in a query. For example, if you have a query like:
SELECT EMPNO, ENAME,SAL FROM EMP ORDER BY SAL DESC
If you want to retrieve the top 5 employees by salary, you can write a query like:
SELECT * FROM ( SELECT X.*,ROWNUM AS RN FROM ( SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL DESC ) X ) WHERE RN <= 5
which gives you results like:
EMPNO ENAME SAL RN ---------- -------- ------ ---- 7839 KING 5000 1 7788 SCOTT 3000 2 7902 FORD 3000 3 7566 JONES 2975 4 7698 BLAKE 2850 5
The feature is not only limited to retrieving the first N
rows in order. By adjusting the criteria in the outmost WHERE
clause you can efficiently retrieve any range of rows in the query's sorted order. For example, to retrieve rows 6
through 10
you could alter the query this way:
SELECT * FROM ( SELECT X.*,ROWNUM AS RN FROM ( SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL DESC ) X ) WHERE RN BETWEEN 6 AND 10
Generalizing this idea, if you want to see page number P
of the query results, where each page contains R
rows, then you would write a query like:
SELECT * FROM ( SELECT X.*,ROWNUM AS RN FROM ( SELECT EMPNO,ENAME,SAL FROM EMP ORDER BY SAL DESC ) X ) WHERE RN BETWEEN ((:P - 1) * :R) + 1 AND (:P) * :R
As the result set you consider grows larger and larger, it becomes more and more efficient to use this technique to page through the rows. Rather than retrieving hundreds or thousands of rows over the network from the database, only to display ten of them on the page, instead you can produce a clever query to retrieve only the R
rows on page number P
from the database. No more than a handful of rows at a time needs to be returned over the network when you adopt this strategy.
To implement this database-centric paging strategy in your application, you could handcraft the clever query yourself and write code to manage the appropriate values of the :R
and :P
bind variables. Alternatively, you can use the view object's range paging access mode, which implements it automatically for you.
To enable range paging for your view object, first call setRangeSize()
to define the number of rows per page, then call the following method:
yourViewObject.setAccessMode(RowSet.RANGE_PAGING);
When a view object's access mode is set to RANGE_PAGING
, the view object takes its default query like:
SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC
and automatically "wraps" it to produce a Top-N query.
For best performance, the statement uses a combination of greater than and less than conditions instead of the BETWEEN
operator, but the logical outcome is the same as the Top-N wrapping query you saw above. The actual query produced to wrap a base query of:
SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC
looks like this:
SELECT * FROM ( SELECT /*+ FIRST_ROWS */ IQ.*, ROWNUM AS Z_R_N FROM ( SELECT EMPNO, ENAME, SAL FROM EMP ORDER BY SAL DESC ) IQ WHERE ROWNUM < :0) WHERE Z_R_N > :1
The two bind variables are bound as follows:
:1
index of the first row in the current page
:0
is bound to the last row in the current page
When a view object operates in RANGE_PAGING
access mode, it only keeps the current range (or "page") of rows in memory in the view row cache at a time. That is, if you are paging through results ten at a time, then on the first page, you'll have rows 1 through 10 in the view row cache. When you navigate to page two, you'll have rows 11 through 20 in the cache. This also can help make sure for large row sets that you don't end up with tons of rows cached just because you want to preserve the ability to scroll backwards and forwards.
When a view object operates in RANGE_PAGING
access mode, to scroll to page number N
call its scrollToRangePage()
method, passing N
as the parameter value.
When a view object operates in RANGE_PAGING
access mode, you can access an estimate of the total number of pages the entire query result would produce using the getEstimatedRangePageCount()
method.
The range paging access mode is typically used for paging through read-only row sets, and often is used with read-only view objects. You allow the user to find the row they are looking for by paging through a large row set with range paging access mode, then you use the Key
of that row to find the selected row in a different view object for editing.
Additionally, the view object supports a RANGE_PAGING_AUTO_POST
access mode to accommodate the inserting and deleting of rows from the row set. This mode behaves like the RANGE_PAGING
mode, except that it eagerly calls postChanges()
on the database transaction whenever any changes are made to the row set. This communicates the pending changes to the database via appropriate INSERT
, UPDATE
, or DELETE
statements so that the changes are preserved when scrolling backward or forward.
You might ask yourself, "Why wouldn't I always want to use RANGE_PAGING
mode?" The answer is that using range paging potentially causes more overall queries to be executed as you are navigating forward and backward through your view object rows. You would want to avoid using RANGE_PAGING
mode in these situations:
You plan to read all the rows in the row set immediately (for example, to populate a dropdown list).
In this case your range size would be set to -1
and there really is only a single "page" of all rows, so range paging does not add value.
You need to page back and forth through a small-sized row set.
If you have 100 rows or fewer, and are paging through them 10 at a time, with RANGE_PAGING
mode you will execute a query each time you go forward and backward to a new page. In normal mode, you will cache the view object rows as you read them in, and paging backwards through the previous pages will not re-execute queries to show those already-seen rows.
In the case of a very large (or unpredictably large) row set, the trade off of potentially doing a few more queries — each of which only returns up to the RangeSize
number of rows from the database — is more efficient then trying to cache all of the previously-viewed rows. This is especially true if you allow the user to jump to an arbitrary page in the list of results. Doing so in normal, scrollable mode requires fetching and caching all of the rows between the current page and the page the users jumps to. In RANGE_PAGING
mode, it will ask the database just for the rows on that page. Then, if the user jumps back to a page of rows that they have already visited, in RANGE_PAGING
mode, those rows get re-queried again since only the current page of rows is held in memory in this mode.
When useful, you can set up your data model to have multiple master view object instances for the same detail view object instance. Consider view objects named Technicians
, Products
, and ExpertiseAreas
with view links defined between:
Products
and ExpertiseAreas
Technicians
and ExpertiseAreas
Note: The example in this section refer to theMultipleMasters project in the AdvancedViewObjectExamples workspace. See the note at the beginning of this chapter for download instructions. |
Figure 27-1 shows what the data model panel looks like when you've configured both Technicians
and Products
view object instances to be masters of the same ExpertiseAreas
view object instance.
To set up the data model as shown in Figure 27-1 open the Application Module Editor and follow these steps on the Data Model panel:
Add an instance of the Technicians
view object to the data model.
Assume you name it Technicians
.
Add an instance of the Products
view object to the data model
Assume you name it Products
.
Select the Technicians
view object instance in the Data Model list
In the Available View Objects list, select the ExpertiseAreas
view object indented beneath the Technicians
view object, enter the view object instance name of ExpertiseAreas
in the Name field, and click > to shuttle it into data model as a detail of the existing Technicians
view object instance.
Select the Products
view object instance in the Data Model list
In the Available View Objects list, select the ExpertiseAreas
view object indented beneath the Products
view object, enter the same view object instance name of ExpertiseAreas
in the Name field, and click > to shuttle it into data model as a detail of the existing Products
view object instance.
An alert will appear: An instance of a View Object with the name ExpertiseAreas has already been used in the data model. Would you like to use the same instance?
Click Yes to confirm you want the ExpertiseAreas
view object instance to also be the detail of the Products
view object instance.
View objects based on multiple entity usages support the ability to find view rows by specifying a partially populated key. A partial key is a multi-attribute Key
object with some of its attributes set to null
. However, there are strict rules about what kinds of partial keys can be used to perform the findByKey()
.
If a view object is based on N
entity usages, where N
> 1, then the view row key is by default comprised of all of the primary key attributes from all of the participating entity usages. Only the ones from the first entity object are required to participate in the view row key, but by default all of them do.
If you allow the key attributes from some secondary entity usages to remain as key attributes at the view row level, then you should leave all of the attributes that form the primary key for that entity object as part of the view row key. Assuming you have left the one or more key attributes in the view row for M
of the N
entity usages, where (M
<= N
), then you can use findByKey()
to find rows based on any subset of these M
entity usages. Each entity usage for which you provide values in the Key
object, requires that you must provide non-null values for all of the attributes in that entity's primary key.
You have to follow this rule because when a view object is based on at least one or more entity usages, its findByKey()
method finds rows by delegating to the findByPrimaryKey()
method on the entity definition corresponding to the first entity usage whose attributes in the view row key are non-null. The entity definition's findByPrimaryKey()
method requires all key attributes for any given entity object to be non-null in order to find the entity row in the cache.
As a concrete example, imagine that you have a ServiceRequests
view object with an ServiceRequest
entity object as its primary entity usage, and a Product
entity as secondary reference entity usage. Furthermore, assume that you leave the Key Attribute property of both of the following view row attributes set to true
:
SvrId
— primary key for the ServiceRequest
entity
ProdId1
— primary key for the Product
entity
The view row key will therefore be the (SvrId
, ProdId1
) combination. When you do a findByKey()
, you can provide a Key
object that provides:
A completely specified key for the underlying ServiceRequest
entity
Key k = new Key(new Object[]{new Number(200), null});
A completely specified key for the underlying Product
entity
Key k = new Key(new Object[]{null, new Number(118)});
A completely specified key for both entities
Key k = new Key(new Object[]{new Number(200), new Number(118)});
When a valid partial key is specified, the findByKey()
method can return multiple rows as a result, treating the missing entity usage attributes in the Key object as a wildcard.
You can add one or more dynamic attributes to a view object at runtime using the addDynamicAttribute()
method. Dynamic attributes can hold any object as their value. Typically, you will consider using dynamic attributes when writing generic framework extension code that requires storing some additional per-row transient state to implement a feature you want to add to the framework in a global, generic way.
While you typically work with a view object's default row set, you can call the createRowSet()
method on the ViewObject
interface to create secondary, named row sets based on the same view object's query. One situation where this could make sense is when your view object's SQL query contains named bind variables. Since each RowSet
object stores its own copy of bind variable values, you could use a single view object to produce and process multiple row sets based on different combinations of bind variables values. You can find a named row set you've created using the findRowSet()
method. When you're done using a secondary row set, call its closeRowSet()
method.
For any RowSet
, while you typically work with its default row set iterator, you can call the createRowSetIterator()
method of the RowSet
interface to create secondary, named row set iterators. You can use find a named row set iterator you've created using the findRowSetIterator()
method. When you're done using a secondary row set iterator, call its closeRowSetIterator()
method.
Note: Through the ADF Model declarative data binding layer, user interface pages or panels in your application work with the default row set iterator of the default row set of view objects in the application module's data model. Due to this fact, the most typical scenario for creating secondary row set iterators is to write business logic that iterates over a view object's default row set without disturbing the current row of the default row set iterator used by the user interface layer. |
Each time you retrieve a view link accessor row set, by default the view object creates a new RowSet
object to allow you to work with the rows. This does not imply re-executing the query to produce the results each time, only creating a new instance of a RowSet
object with its default iterator reset to the "slot" before the first row. To force the row set to refresh its rows from the database, you can call its executeQuery()
method.
Since there is a small amount of overhead associated with creating the row set, if your code makes numerous calls to the same view link accessor attributes you can consider enabling view link accessor row set retention for the source view object in the view link. To use the view link accessor retention feature, enable a custom Java class for your view object, override the create()
method, and add a line after super.create()
that calls the setViewLinkAccessorRetained()
method passing true
as the parameter. It affects all view link accessor attributes for that view object.
When this feature is enabled for a view object, since the view link accessor row set it not recreated each time, the current row of its default row set iterator is also retained as a side-effect. This means that your code will need to explicitly call the reset()
method on the row set you retrieve from the view link accessor to reset the current row in its default row set iterator back to the "slot" before the first row.
Note, however, that with accessor retention enabled, your failure to call reset()
each time before you iterate through the rows in the accessor row set can result in a subtle, hard-to-detect error in your application. For example, if you iterate over the rows in a view link accessor row set like this, for example to calculate some aggregate total:
RowSet rs = (RowSet)row.getAttribute("ServiceRequestsForProduct"); while (rs.hasNext()) { Row r = rs.next(); // Do something important with attributes in each row }
The first time you work with the accessor row set the code will work. However, since the row set (and its default row set iterator) are retained, the second and subsequent times you access the row set the current row will already be at the end of the row set and the while loop will be skipped since rs.hasNext()
will be false
. Instead, with this feature enabled, write your accessor iteration code like this:
RowSet rs = (RowSet)row.getAttribute("ServiceRequestsForProduct");
rs.reset(); // Reset default row set iterator to slot before first row!
while (rs.hasNext()) {
Row r = rs.next();
// Do something important with attributes in each row
}
Recall that if view link consistency is on, when the accessor is retained the new unposted rows will show up at the end of the row set. This is slightly different from when the accessor is not retained (the default), where new unposted rows will appear at the beginning of the accessor row set.