How to use cursors in the geodatabase


Summary
This article discusses the use of cursors in the geodatabase and demonstrates when and how they can be used.

In this topic


Traits common to all cursor objects

A cursor is a data access object that can be used to iterate over the set of rows in a table or insert new rows into a table. The following are the three different forms of cursor objects:
Each of these types of cursors is returned by the corresponding method (Search, Insert, or Update) on a Table or FeatureClass object. The Search and Update methods take a QueryFilter as input that can be used to restrict the set of rows returned. See the following illustration:
All of these methods are available in the single ICursor interface. Therefore, it is the application developer's responsibility to make the method calls appropriate to the type of cursor.
The type of row objects returned by a cursor (the interfaces and methods supported by the row object) depends on the type of table (Object-Class, FeatureClass, or AttributedRelationship class) and its associated behavior. The row objects returned by evaluating a QueryDef are read-only. A QueryDef represents a database query on one or more tables or feature classes. Row objects returned from a QueryDef do not reference a parent table, and the Store method cannot be called on them. Attempting to store a row object returned by evaluating a QueryDef will result in an error.

Reaching the end of a cursor

To retrieve all rows in a cursor containing N rows, the application must make N calls to NextRow. In VB6, a call to NextRow after the last row in the result set has been retrieved returns nothing. In C# .NET, this same call to NextRow returns a value of null. Cursors are not scrollable and do not support backing up and retrieving rows that have already been retrieved or making multiple passes over data.

Recycling and cursors

When instantiating a cursor using the Search or Update methods, the caller has the option to choose whether or not the cursor will reuse (recycle) internal resources allocated for the current row. A cursor that is reusing resources is commonly referred to as a recycling cursor.
A cursor that is not reusing resources is commonly referred to as a nonrecycling cursor. Cursors instantiated using the Insert method do not support recycling since they do not support the NextRow method.
 
Recycling cursors offer performance advantages, but they should only be used for reading data, not for writing. Recycling cursors allocate a single row object and rehydrate it on each fetch. They can be used to optimize read-only access, for example, when drawing. Row objects returned by a recycling cursor should not be modified. When using a recycling cursor, the row returned by NextRow is only valid until the next call to NextRow. Any subsequent call to NextRow invalidates the previous row. Callers must release any references to the current row prior to calling NextRow.
 
Nonrecycling cursors return a unique instance for each row returned by NextRow. The rows returned by a nonrecycling cursor can be modified (setting the IRow.Value property or any other custom mutator supported by the row) and stored with polymorphic behavior. The geodatabase guarantees unique instance semantics on nonrecycling row objects fetched during an edit session.
If the row object to be retrieved by a call to NextRow has already been instantiated in the calling application, a reference to the existing row object will be returned. When using a nonrecycling cursor, the row returned by NextRow is valid regardless of subsequent calls to NextRow. Callers can maintain references to the current row even if additional calls to NextRow are invoked.

Editing with cursors

Existing features can be edited with either a search cursor or an update cursor.  When using a search cursor, NextFeature is called, returning a reference to a feature.  The feature is edited, and IFeature.Store is called.  When using an update cursor, NextFeature is called, the feature is edited, and UpdateFeature is called with the feature as a parameter.  It is important to remember, when using an update cursor, do not call the Store method.
The recommended approach depends on whether or not the edits are being made in an edit session, if the cursor is being used by ArcMap or by an Engine application, and if the features being edited are simple or complex.  The table below shows which approach to use in different situations:
 
ArcMap
Engine - Simple
Engine - Complex
Inside edit sessions
Search
Search
Search
Outside edit sessions
Search
Update
Search
A search cursor is always recommended in ArcMap, because the query may be satisfied by the contents of the map cache, making a DBMS query unnecessary.
When working with simple features and edit sessions in an Engine application, it's recommended that a search cursor be used to take advantage of batched updates in edit operations.  Outside of edit sessions, an update cursor allows performance and error handling advantages.  With complex features, on the other hand, update calls are overridden by the features' custom behavior, meaning that the feature's store method will be called even if an update cursor is used.
When using cursors within edit sessions, cursors should be scoped to edit operations.  See the following code example, which changes the "TYPE" value in the features of a roads feature class to "Autoroute":
[Java]
static void editWithSearchCursor(IWorkspace workspace, IFeatureClass featureClass,
    IQueryFilter queryFilter)throws Exception{
    // Find the index of the field to edit
    int typeFieldIndex = featureClass.findField("TYPE");
    // Cast the workspace to an IWorkspaceEdit and start an edit session.
    IWorkspaceEdit workspaceEdit = (IWorkspaceEdit)workspace;
    workspaceEdit.startEditing(true);
    // Start an edit operation and create a cursor.
    workspaceEdit.startEditOperation();
    // Create a search cursor.
    IFeatureCursor searchCursor = featureClass.search(queryFilter, false);
    // Edit the first feature found.
    IFeature feature = searchCursor.nextFeature();
    if (feature != null){
        feature.setValue(typeFieldIndex, "Autoroute");
        feature.store();
    }
    // Since the edit operation is ending, the cursor should be released.
    Cleaner.release(searchCursor);
    // Stop the edit operation.
    workspaceEdit.stopEditOperation();
    // Stop the edit session.
    workspaceEdit.stopEditing(true);
}
Using the Cleaner class to Release Cursors
When writing code that uses cursors, it is important to call the static method release(object) from the Cleaner class once the cursor is no longer being used.  The Cleaner class is contained in the com.esri.arcgis.system package.

Search cursor object

A Search cursor can be used to retrieve rows specified by a QueryFilter and supports a NextRow method. This type of cursor can also be used to get a reference row to make updates calling the Store method.
A Search cursor can be used in an edit session to fetch rows with the intent to update row values. Search cursors that are used to update row values must be instantiated as nonrecycling cursors. Updating row values using a Search cursor is accomplished using a simple methodology of fetching the rows using a nonrecycling cursor, setting the desired row values, and calling the Store method on the row.
 
A Search cursor is used to find all the features in a feature class of towns and cities that intersect a spatial envelope and are classified as a city in the following code example:
[Java]
static void useSearchCursor(IFeatureClass featureClass)throws Exception{
    // Create an envelope for the lower-right portion of data.
    IEnvelope envelope = new Envelope();
    envelope.putCoords( - 66.4, 43.5,  - 61.9, 46.9);
    // Create a spatial query filter.
    ISpatialFilter spatialFilter = new SpatialFilter();
    // Specify the geometry to query with.
    spatialFilter.setGeometryByRef(envelope);
    // Specify what the geometry field is called on the feature class that you will query against.
    spatialFilter.setGeometryField(featureClass.getShapeFieldName());
    // Specify the type of spatial operation to use.
    spatialFilter.setSpatialRel(esriSpatialRelEnum.esriSpatialRelIntersects);
    // Create the Where statement.
    spatialFilter.setWhereClause("TERM = 'City'");
    // Perform the query and use a cursor to hold the results.
    //IQueryFilter queryFilter = (IQueryFilter)spatialFilter;
    IFeatureCursor searchCursor = featureClass.search(spatialFilter, true);
    // Iterate through the features, viewing (as in this case) or editing them.
    IFeature feature = null;
    int nameFieldIndex = featureClass.findField("NAME");
    while ((feature = searchCursor.nextFeature()) != null){
        System.out.println("City found: " + feature.getValue(nameFieldIndex));
    }
    // If the cursor is no longer needed, release it.
    Cleaner.release(searchCursor);
}

Update cursor object

An Update cursor can be used to update and delete rows based on position and supports the NextRow, UpdateRow, and DeleteRow methods. Similar methods exist for feature cursors (NextFeature, UpdateFeature, and DeleteFeature).
UpdateRow method
The UpdateRow method can be used to update the row at the current position of an Update cursor—making a call to NextRow on a cursor returns a row and positions the cursor on that row. After fetching a row object using NextRow, the application can modify the row as needed and call UpdateRow, passing in the modified row. This is an alternative to calling the Store method on the retrieved row.
Using a nonrecycling Update cursor can be faster than calling the Store method on the rows returned by a Searchcursor when performing direct updates outside an edit session on simple data.
If the row objects for the table are not simple (objects have custom behavior, such as geometric networks, or they participate in composite relationships or relationships with notification), calling UpdateRow on the cursor generates a call to the Store method on the row object to trigger the custom behavior—there will be no performance gain.
In the following code example, an Update cursor is used to change the values of the "TYPE" field to "Toll Highway" in a feature class of roads, where the name of the road is "Highway 104":
[Java]
static void useUpdateCursor(IFeatureClass featureClass)throws Exception{
    // Restrict the number of features to be updated.
    IQueryFilter queryFilter = new QueryFilter();
    queryFilter.setWhereClause("NAME = 'Highway 104'");
    // Use IFeatureClass.Update to populate IFeatureCursor.
    IFeatureCursor updateCursor = featureClass.IFeatureClass_update(queryFilter,
        false);
    int typeFieldIndex = featureClass.findField("TYPE");
    IFeature feature = null;
    while ((feature = updateCursor.nextFeature()) != null){
        feature.setValue(typeFieldIndex, "Toll Highway");
        updateCursor.updateFeature(feature);
    }
    // If the cursor is no longer needed, release it.
    Cleaner.release(updateCursor);
}
DeleteRow method
The DeleteRow method can be used to delete the row at the current position of an Update cursor and deletes the row returned by the last call to the NextRow method on this cursor.
After fetching the row object using NextRow, the application should call DeleteRow on the cursor to delete the row. The application is responsible for releasing any outstanding references on the deleted row. Using a recycling Update cursor to delete rows can be faster than calling delete on the rows returned by a Search cursor. Much like calling UpdateRow, this is often the case when performing direct updates outside an edit session on simple data.
If the row objects for the table are not simple, DeleteRow on the cursor generates a call to the Delete method on the row object to trigger custom behavior—there will be no performance gain.
The following code example uses an Update cursor to delete all features in a feature class of cities and towns with values beginning like "Vill" in the "TERM" field:
[Java]
static void deleteRows(IFeatureClass featureClass)throws Exception{
    IQueryFilter queryFilter = new QueryFilter();
    queryFilter.setWhereClause("TERM LIKE 'Vill%'");
    // Use IFeatureClass.Update to populate IFeatureCursor.
    IFeatureCursor updateCursor = featureClass.IFeatureClass_update(queryFilter,
        false);
    IFeature feature = null;
    while ((feature = updateCursor.nextFeature()) != null){
        updateCursor.deleteFeature();
    }
    // If the cursor is no longer needed, release it.
    Cleaner.release(updateCursor);
}

Insert cursor object

An Insert cursor is used to insert rows into a table and supports the InsertRow method. Insert cursors are used to bulk insert rows. Using an Insert cursor offers significantly faster performance for data loading into simple tables and feature classes—tables whose class identifier (CLSID) is esriGeodatabase.Row, esriGeodatabase.Object, or esriGeodatabase.Feature—than the alternative of making multiple calls to CreateRow on the table followed by calling the Store method on the created row.
Insert cursors on tables that contain custom rows and objects internally use the CreateRow and Store methods to achieve polymorphism—there is no difference in performance in these cases.
The InsertRow method takes a row buffer as an argument. Applications obtain a row buffer using the CreateRowBuffer method on the table object where rows are inserted. Each call to InsertRow on the cursor creates a new row in the database whose initial values are set to the values in the input row buffer.
The object ID for the created row is returned by the InsertRow method. The UseBuffering method argument to the Insert method on a table returns an Insert cursor that buffers rows on the client side and sends them to the server in batches for increased performance.
The row buffer created and used in the InsertRow call can also be reused. The application is responsible for calling the Flush method on the Insert cursor after all rows have been inserted. If a call to the Flush method is not made, the cursor flushes its buffers on destruction (when the application releases all references on the cursor). However, relying on the destructor to flush the Insert cursor does not give the application the chance to detect errors that can arise on the call to the Flush method, for example, if the storage for the table in the underlying database fills up.  The next code example provides a basic template for how to check whether or not a flush is successful.
The following code example uses a FeatureBuffer to buffer a set of new features into a feature class of roads. The number of features is determined by the size of the geometryList parameter, and the features take their Shape properties sequentially from the list.  All of the features have their "TYPE" value set to "Primary Highway".  These inserts are stored in a buffer and are only flushed when Flush is called:
[Java]
static void insertFeaturesUsingCursor(IFeatureClass featureClass, List < IGeometry >
    geometryList)throws Exception{
    // Create the feature buffer and find the index of the TYPE field.
    IFeatureBuffer featureBuffer = featureClass.createFeatureBuffer();
    int typeFieldIndex = featureClass.findField("TYPE");
    // Create insert feature cursor using buffering = true.
    IFeatureCursor insertCursor = featureClass.IFeatureClass_insert(true);
    // All of the features to be created are classified as Primary Highways.
    featureBuffer.setValue(typeFieldIndex, "Primary Highway");
    for (int i = 0; i < geometryList.size(); i++){
        // Set the feature buffer's shape and insert it.
        featureBuffer.setShapeByRef(geometryList.get(i));
        insertCursor.insertFeature(featureBuffer);
    }
    // Attempt to flush the buffer to the geodatabase.
    insertCursor.flush();
    Cleaner.release(insertCursor);
}


See Also:

How to query geodatabase objects
How to join data




Development licensingDeployment licensing
ArcGIS for Desktop BasicArcGIS for Desktop Basic
ArcGIS for Desktop StandardArcGIS for Desktop Standard
ArcGIS for Desktop AdvancedArcGIS for Desktop Advanced
Engine Developer KitEngine