Updating features


Summary This topic explains how to updates features in a geodatabase feature class. Two approaches are shown, one that updates an individual feature using the IFeature.Store method, and one that uses a cursor to update multiple features at once. Other topics include how to delete features and how to optimize performance in certain situations.

In this topic


About updating features

The functionality available for updating features is dependent on the following:
  • The license level the code runs on and the type of geodatabase it runs against.
  • The type of features that are being updated—whether they are simple features, network features, created in a topology, and so on. For example, an ArcEditor or ArcInfo license is required to update features in the following:
    • Geometric networks or topologies
    • Dimension feature classes
    • Annotation feature classes
    • ArcSDE geodatabases, whether at the personal, workgroup, or enterprise level
Updating features typically falls into one of two scenarios:
  • A feature has already been retrieved - for example, through a map tool - and the application will use the IFeature interface to modify its geometry and/or attributes then persist those changes to the database.
  • Multiple features must be updated - for example every feature in a feature class, or every feature that satisfies a spatial or attribute constraint. In these cases, an update cursor or a search cursor should be used to update the features.
The workflow to delete features is similar to that required to update features, and is also discussed below.
Note that many types of updates require an edit session, for example updating features that participate in a topology or geometric network. For more information about edit sessions, see Editing with the geodatabase API.

Updating individual features

There are many cases when developing in ArcGIS where an application will get an IFeature reference to a single feature. A simple example of this is when the application has a known Object ID for a feature in a feature class, and uses IFeatureClass.GetFeature to retrieve that feature.
Once an application has an IFeature reference, there are two primary properties that can be used to modify the feature - Shape and Value - which change the geometry and attributes, respectively. When setting the Shape property, use a geometry that matches the feature class geometry type. Value requires a parameter indicating the position of the attribute to modify and should be passed a value matching the field type (for example, pass System.DateTime values to DateTime fields).
After a feature has been appropriately modified, the Store method can be called to push the changes to the geodatabase.
The following code example shows how to retrieve a single feature using a known Object ID, assign it a new point as its geometry, modify a single string field, then store the changes:
[C#]
// Get a known feature using its Object ID.
IFeature feature = featureClass.GetFeature(1);

// Assign a new point as the feature's shape.
IPoint newLocation = new PointClass
{
    X = 1000, Y = 1000
};
feature.Shape = newLocation;

// Find the position of the "NAME" field and modify it.
int nameFieldIndex = featureClass.FindField("NAME");
feature.set_Value(nameFieldIndex, "My Value");

// Store the changes.
feature.Store();
[VB.NET]
' Get a known feature using its Object ID.
Dim feature As IFeature = featureClass.GetFeature(1)

' Assign a new point as the feature's shape.
Dim newLocation As IPoint = New PointClass With {.X = 1000, .Y = 1000}
feature.Shape = newLocation

' Find the position of the "NAME" field and modify it.
Dim nameFieldIndex As Integer = featureClass.FindField("NAME")
feature.Value(nameFieldIndex) = "My Value"

' Store the changes.
feature.Store()
To delete a single feature call the IFeature.Delete method. After calling Delete, do not call Store.
A good practice when using the FindField method (as shown above) is to check whether the return value is greater than or equal to zero. If a value of -1 is returned, it indicates that the requested field could not be found; the application should then handle this problem rather than using the value in subsequent calls. The code examples in this article are simplified for readability and do not check for this case.

Using cursors to perform updates

To get the best performance out of an application, cursors should be used to perform updates whenever multiple features are being modified. Both update cursors and search cursors can be used to perform updates; the recommended approach depends on three factors:
  • Application - Is it ArcMap or an Engine application?
  • Editing - Is an edit session being used?
  • Geodatabase - Is the geodatabase a local geodatabase or an ArcSDE geodatabase?
The table below shows which type of cursor to use in different situations:
 
ArcMap
ArcGIS Engine
Inside edit sessions
Search cursor
ArcSDE: Search cursor
Local GDB: Update cursor
Outside edit sessions
Search cursor
Update cursor
When using cursors in the .NET framework it is important to release a cursor when it's no longer needed. There are two ways to do this:
  • Using the Marshal.ReleaseComObject method, found in the System.Runtime.InteropServices namespace of the .NET class libraries.
  • Using the disposable ComReleaser class, found in the ArcGIS .NET ADF.
Both approaches are discussed in the article Releasing COM references. The examples in this article will demonstrate how to manage cursors using the ComReleaser class.
Several of the examples below use query filters to restrict the features returned by the cursors. To learn more about how to use query filters, see Querying geodatabase tables.

Using an update cursor

An update cursor can be used to update and delete features based on position and supports the NextFeature, UpdateFeature and DeleteFeature methods. Some methods should not be called on features retrieved with an update cursor, specifically IFeature.Store and IFeature.Delete. Instead, UpdateFeature and DeleteFeature should be used.
The following code example uses an update cursor from a table of employees to generate email addresses based on the first letter of their first name and their last names. For example, "Rusty Shackleford" would be assigned the email address "RShackleford@MyCompanyName.com":
[C#]
// Create a ComReleaser for cursor management.
using(ComReleaser comReleaser = new ComReleaser())
{
    // Use ITable.Update to create an update cursor.
    ICursor updateCursor = table.Update(null, true);
    comReleaser.ManageLifetime(updateCursor);

    // Find the positions of the fields used to get and set values.
    int firstNameIndex = table.FindField("FirstName");
    int lastNameIndex = table.FindField("LastName");
    int emailIndex = table.FindField("Email");
    IRow row = null;
    while ((row = updateCursor.NextRow()) != null)
    {
        // Get the first and last names.
        String firstName = Convert.ToString(row.get_Value(firstNameIndex));
        String lastName = Convert.ToString(row.get_Value(lastNameIndex));

        // Append the first letter of the first name and the entire last name with
        // an "at" symbol and the email domain to make an email address.
        String emailAddress = String.Concat(firstName[0], lastName, 
            "@MyCompanyName.com");
        row.set_Value(emailIndex, emailAddress);
        updateCursor.UpdateRow(row);
    }
}
[VB.NET]
' Create a ComReleaser for cursor management.
Using comReleaser As ComReleaser = New ComReleaser()
' Use ITable.Update to create an update cursor.
Dim updateCursor As ICursor = table.Update(Nothing, True)
comReleaser.ManageLifetime(updateCursor)

' Find the positions of the fields used to get and set values.
Dim firstNameIndex As Integer = table.FindField("FirstName")
Dim lastNameIndex As Integer = table.FindField("LastName")
Dim emailIndex As Integer = table.FindField("Email")
Dim row As IRow = updateCursor.NextRow()
While Not row Is Nothing
    ' Get the first and last names.
    Dim firstName As String = Convert.ToString(row.Value(firstNameIndex))
    Dim lastName As String = Convert.ToString(row.Value(lastNameIndex))
    
    ' Append the first letter of the first name and the entire last name with
    ' an "at" symbol and the email domain to make an email address.
    Dim emailAddress As String = String.Concat(firstName(0), lastName, "@MyCompanyName.com")
    row.Value(emailIndex) = emailAddress
    updateCursor.UpdateRow(row)
    row = updateCursor.NextRow()
End While
End Using
The parameter of the UpdateFeature method should always be the feature that the cursor is pointing to. Passing in a different feature—a newly instantiated feature, a feature from a different position in the feature class, or a feature from another feature class—should never occur.

Using a search cursor

Updating features retrieved by a search cursor is similar to updating individual features. After calling NextFeature and modifying the returned feature, use IFeature.Store to push the changes to the geodatabase, then call NextFeature and repeat the process.
When using a search cursor to perform updates, recycling should always be disabled. This is controlled through the boolean parameter of the Search method.
The following example shows how to use a search cursor to update every road in a feature class so that those with two lanes have a speed limit of 50 while those with more have a speed limit of 80:
[C#]
// Create a ComReleaser for cursor management.
using(ComReleaser comReleaser = new ComReleaser())
{
    // Use IFeatureClass.Search to create a search cursor.
    IFeatureCursor searchCursor = featureClass.Search(null, false);
    comReleaser.ManageLifetime(searchCursor);

    // Find the positions of the fields used to get and set values.
    int laneFieldIndex = featureClass.FindField("LANE_COUNT");
    int speedFieldIndex = featureClass.FindField("SPEED_LIMIT");
    IFeature feature = null;
    while ((feature = searchCursor.NextFeature()) != null)
    {
        // Check the lane count of the feature.
        int laneCount = Convert.ToInt32(feature.get_Value(laneFieldIndex));

        // Set the speed limit based on the lane count.
        int speedLimit = (laneCount == 2) ? 50 : 80;
        feature.set_Value(speedFieldIndex, speedLimit);
        feature.Store();
    }
}
[VB.NET]
' Create a ComReleaser for cursor management.
Using comReleaser As ComReleaser = New ComReleaser()
' Use IFeatureClass.Search to create a search cursor.
Dim searchCursor As IFeatureCursor = featureClass.Search(Nothing, False)
comReleaser.ManageLifetime(searchCursor)

' Find the positions of the fields used to get and set values.
Dim laneFieldIndex As Integer = featureClass.FindField("LANE_COUNT")
Dim speedFieldIndex As Integer = featureClass.FindField("SPEED_LIMIT")
Dim feature As IFeature = searchCursor.NextFeature()
While Not feature Is Nothing
    ' Check the lane count of the feature.
    Dim laneCount As Integer = Convert.ToInt32(feature.Value(laneFieldIndex))
    
    ' Set the speed limit based on the lane count.
    Dim speedLimit As Integer = If(laneCount = 2, 50, 80)
    feature.Value(speedFieldIndex) = speedLimit
    feature.Store()
    feature = searchCursor.NextFeature()
End While
End Using

Deleting features

The best approach to take when deleting features depends on two factors, how many features are being deleted and whether the data source is a local geodatabase or an ArcSDE geodatabase.
In the simplest case, a single feature that has already been retrieved can be deleted by calling IFeature.Delete. If bulk features are being deleted and the geodatabase is an ArcSDE geodatabase, the most efficient approach requires the use of a search cursor and the IFeature.Delete method:
[C#]
// Define a constraint on the features to be deleted.
IQueryFilter queryFilter = new QueryFilterClass
{
    WhereClause = "TERM = 'Village'"
};

// Create a ComReleaser for cursor management.
using(ComReleaser comReleaser = new ComReleaser())
{
    // Create and manage a cursor.
    IFeatureCursor searchCursor = featureClass.Search(queryFilter, false);
    comReleaser.ManageLifetime(searchCursor);

    // Delete the retrieved features.
    IFeature feature = null;
    while ((feature = searchCursor.NextFeature()) != null)
    {
        feature.Delete();
    }
}
[VB.NET]
' Define a constraint on the features to be deleted.
Dim queryFilter As IQueryFilter = New QueryFilterClass With _
                                  {.WhereClause = "TERM = 'Village'"}

' Create a ComReleaser for cursor management.
Using comReleaser As ComReleaser = New ComReleaser()
' Create and manage a cursor.
Dim searchCursor As IFeatureCursor = featureClass.Search(queryFilter, False)
comReleaser.ManageLifetime(searchCursor)

' Delete the retrieved features.
Dim feature As IFeature = searchCursor.NextFeature()
While Not feature Is Nothing
    feature.Delete()
    feature = searchCursor.NextFeature()
End While
End Using
On the other hand, if the geodatabase is a local geodatabase (a file or personal geodatabase), the most efficient method for bulk deletion is the ITable.DeleteSearchedRows method:
[C#]
// Define a constraint on the features to be deleted.
IQueryFilter queryFilter = new QueryFilterClass
{
    WhereClause = "TERM = 'Village'"
};

// Cast the feature class to the ITable interface.
ITable table = (ITable)featureClass;
table.DeleteSearchedRows(queryFilter);
[VB.NET]
' Define a constraint on the features to be deleted.
Dim queryFilter As IQueryFilter = New QueryFilterClass With _
                                  {.WhereClause = "TERM = 'Village'"}

' Cast the feature class to the ITable interface.
Dim table As ITable = CType(featureClass, ITable)
table.DeleteSearchedRows(queryFilter)

Performance and correctness considerations

Besides deciding whether an update or search cursor will result in better performance, the main consideration to take into account when performing updates is whether to enable or disable recycling when using an update cursor. In simple cases such as those shown earlier in this article, enabling recycling is safe and will result in better performance. Cases where recycling should not be used are those where multiple features need to be read at once. For more information about recycling, see Understanding recycling in Geodatabase API best practices.
An additional consideration is how FindField is used in relation to the cursor. In several of the examples above, FindField is used to get a field index, as this is preferable to hard-coding a numeric field index. Note that in every case, it is used outside of the loop where NextFeature is called. While FindField isn't an expensive call, there is no need to call it inside of a loop, and the performance costs can add up if enough features are being updated. For more information about this, see Storing FindField results in Geodatabase API best practices.

UpdateSearchedRows

In an earlier example showing how to use an update cursor, the values being updated on each feature were the result of a function that created a new string based on two other string fields. When performing bulk updates on a local geodatabase class where the updated values are the same for every object, ITable.UpdateSearchedRows will result in better performance.
When using UpdateSearchedRows, it is extremely important to provide a query filter that includes each of the fields that will be edited in its subfields list. UpdateSearchedRows determines which fields should be updated using the subfields list; if it contains fields that are not set in the feature buffer, all of the features will be assigned a null value for those fields. Likewise, if the feature buffer has values set for fields that are not included in the subfields list, these fields will not be updated.
The following example shows how to update a roads feature class so that every road with four lanes is marked as a highway:
[C#]
// Find the position of the field that will be updated.
int typeFieldIndex = featureClass.FindField("TYPE");

// Create a query filter defining which fields will be updated
// (the subfields) and how to constrain which rows are updated
// (the where clause).
IQueryFilter queryFilter = new QueryFilterClass
{
    SubFields = "TYPE", WhereClause = "LANE_COUNT = 4"
};

// Create a ComReleaser for buffer management.
using(ComReleaser comReleaser = new ComReleaser())
{
    // Create a feature buffer containing the values to be updated.
    IFeatureBuffer featureBuffer = featureClass.CreateFeatureBuffer();
    featureBuffer.set_Value(typeFieldIndex, "Highway");
    comReleaser.ManageLifetime(featureBuffer);

    // Cast the class to ITable and perform the updates.
    ITable table = (ITable)featureClass;
    IRowBuffer rowBuffer = (IRowBuffer)featureBuffer;
    table.UpdateSearchedRows(queryFilter, rowBuffer);
}
[VB.NET]
' Find the position of the field that will be updated.
Dim typeFieldIndex As Integer = featureClass.FindField("TYPE")

' Create a query filter defining which fields will be updated
' (the subfields) and how to constrain which rows are updated
' (the where clause).
Dim queryFilter As IQueryFilter = New QueryFilterClass With _
                                  { _
                                  .SubFields = "TYPE", _
                                  .WhereClause = "LANE_COUNT = 4" _
                                  }

' Create a ComReleaser for buffer management.
Using comReleaser As ComReleaser = New ComReleaser()
' Create a feature buffer containing the values to be updated.
Dim featureBuffer As IFeatureBuffer = featureClass.CreateFeatureBuffer()
featureBuffer.Value(typeFieldIndex) = "Highway"
comReleaser.ManageLifetime(featureBuffer)

' Cast the class to ITable and perform the updates.
Dim table As ITable = CType(featureClass, ITable)
Dim rowBuffer As IRowBuffer = CType(featureBuffer, IRowBuffer)
table.UpdateSearchedRows(queryFilter, rowBuffer)
End Using

Scoping edit operations with search cursors

When using a search cursor to edit an ArcSDE dataset, the scope of edit operations can have a significant effect on performance. Edits to ArcSDE made using a search cursor are optimized by the use of a transmission buffer; the edits are pushed to the database when the buffer is filled or when an edit operation is stopped. In other words, if an edit operation is being started and stopped every time a feature is edited the application will fail to perform as well as an application that performs one hundred edits for each edit operation. Consider the two following code excerpts illustrate the difference between these two approaches:
[C#]
// One edit operation for every edit.
while ((feature = cursor.NextFeature()) != null)
{
    workspaceEdit.StartEditOperation();
    feature.set_Value(1, "abcde");
    feature.Store();
    Marshal.ReleaseComObject(feature);
    workspaceEdit.StopEditOperation();
}

// One edit operation for every 500 edits.
int count = 0;
while ((feature = cursor.NextFeature()) != null)
{
    count++;
    workspaceEdit.StartEditOperation();
    feature.set_Value(1, "abcde");
    feature.Store();
    Marshal.ReleaseComObject(feature);
    if (count % 500 == 0)
    {
        workspaceEdit.StopEditOperation();
    }
}

workspaceEdit.StopEditOperation();
[VB.NET]
' One edit operation for every edit.
While Not feature Is Nothing
    workspaceEdit.StartEditOperation()
    feature.Value(1) = "abcde"
    feature.Store()
    Marshal.ReleaseComObject(feature)
    workspaceEdit.StopEditOperation()
    feature = cursor.NextFeature()
End While

' One edit operation for every 500 edits.
Dim Count As Integer = 0
While Not feature Is Nothing
    Count = Count + 1
    workspaceEdit.StartEditOperation()
    feature.Value(1) = "abcde"
    feature.Store()
    Marshal.ReleaseComObject(feature)
    If Count Mod 500 = 0 Then
        workspaceEdit.StopEditOperation()
    End If
    feature = cursor.NextFeature()
End While
workspaceEdit.StopEditOperation()

Truncating tables

The ITableWrite2.Truncate method provides a way to quickly delete every row in a table (or every feature in a feature class). It is only available for local geodatabase classes and for non-versioned classes in ArcSDE. Caution should be taken when using this method, for two reasons:
  • All complex behavior is bypassed when calling this method. Calling it on a dataset that participates in a composite relationship class, participates in a controller dataset such as a topology, or has custom behavior defined (i.e., using a class extension) will likely cause data corruption. This should only be used to truncate simple data.
  • This method ignores schema locks. If the dataset is being used by other users they may experience unexpected behavior.


See Also:

Creating features
Editing with the geodatabase API




To use the code in this topic, reference the following assemblies in your Visual Studio project. In the code files, you will need using (C#) or Imports (VB .NET) directives for the corresponding namespaces (given in parenthesis below if different from the assembly name):
Additional Requirements
  • When updating simple features in an ArcSDE geodatabase, Desktop users require a minimum of an ArcEditor license, while Engine users require the Geodatabase Update.

Development licensing Deployment licensing
ArcView ArcView
ArcEditor ArcEditor
ArcInfo ArcInfo
Engine Developer Kit Engine Runtime