This document was published with and applies to ArcGIS 9.3.
A 10 version also exists.
A 10 version also exists.
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):
ESRI.ArcGIS.ADF ESRI.ArcGIS.Geodatabase ESRI.ArcGIS.System (ESRI.ArcGIS.esriSystem)
In this topic
- Introduction
- Understanding recycling
- Storing FindField results
- Scoping cursors to edit operations
- Performing DDL inside of edit sessions
- Calling Store inside of Store-triggered events
- GetFeature and GetFeatures
- Careless reuse of variables
- Inserts and relationship class notification
- Modifying schema objects
This article is intended to be a "cheat sheet" for developers using the Geodatabase API. There are many best practices that can improve performance and just as many common mistakes that can harm performance or cause unexpected results. The topics in this article are a combination of both, and are based on code found in support incidents, forum posts, and other third-party code.
The code examples in this article are used to illustrate their surrounding paragraphs and not simply to provide code that can by copied and pasted into an application. In some cases the code will be used to illustrate a programming pattern that should be avoided. Prior to copying and pasting code any from this article, ensure from the code's surrounding paragraphs that it is an example of a recommended practice and not an example of something to avoid.
Recycling is a property of cursors that determines how rows from the cursor are created. Recycling can be either enabled or disabled and is exposed through the API as a Boolean parameter on several cursor instantiation methods including ITable.Search and ISelectionSet.Search.
If recycling is enabled, a cursor will only allocate memory for a single row regardless of how many rows are returned from the cursor. This provides performance benefits in terms of both memory usage and running time, but has drawbacks for certain workflows. Where recycling is useful is in situations where only a single row is going to be referenced at any time; for example, drawing geometries or displaying the current row's Object ID to a console window. When multiple rows from a cursor need to be compared in some way, or when rows are being edited, recycling should be avoided. Consider the following example that compares the first two geometries from a feature cursor to see if they're equal:
[C#]
public static void RecyclingInappropriateExample(IFeatureClass featureClass,
Boolean enableRecycling)
{
using(ComReleaser comReleaser = new ComReleaser())
{
// Create a search cursor.
IFeatureCursor featureCursor = featureClass.Search(null, enableRecycling);
comReleaser.ManageLifetime(featureCursor);
// Get the first two geometries and see if they intersect.
IFeature feature1 = featureCursor.NextFeature();
IFeature feature2 = featureCursor.NextFeature();
IRelationalOperator relationalOperator = (IRelationalOperator)
feature1.Shape;
Boolean geometriesEqual = relationalOperator.Equals(feature2.Shape);
Console.WriteLine("Geometries are equal: {0}", geometriesEqual);
}
}
[VB.NET]
Public Shared Sub RecyclingInappropriateExample(ByVal featureClass As IFeatureClass, ByVal enableRecycling As Boolean)
Using comReleaser As ComReleaser = New ComReleaser()
' Create a search cursor.
Dim featureCursor As IFeatureCursor = featureClass.Search(Nothing, enableRecycling)
comReleaser.ManageLifetime(featureCursor)
' Get the first two geometries and see if they intersect.
Dim feature1 As IFeature = featureCursor.NextFeature()
Dim feature2 As IFeature = featureCursor.NextFeature()
Dim relationalOperator As IRelationalOperator = CType(feature1.Shape, IRelationalOperator)
Dim geometriesEqual As Boolean = relationalOperator.Equals(feature2.Shape)
Console.WriteLine("Geometries are equal: {0}", geometriesEqual)
End Using
End Sub
If recycling is enabled, this code will always return true because the feature1 and feature2 references will point to the same object. The second NextFeature call doesn't create a new row, it simply overwrites the existing row's values. The call to IRelationalOperator.Equals is just comparing a geometry to itself. For the same reason, any comparison between the Object ID or attribute values of the "two" features would also indicate equality.
Disabling recycling is a more cautious approach because inappropriate use of a non-recycling cursor is unlikely to return unexpected results like the one in the example above, but it can impose a significant penalty in performance. The following code example opens a search cursor on a feature class and creates a sum of every feature's area. As this doesn't reference any previously fetched rows, this is an ideal candidate for a recycling cursor:
[C#]
public static void RecyclingAppropriateExample(IFeatureClass featureClass,
Boolean enableRecycling)
{
using(ComReleaser comReleaser = new ComReleaser())
{
// Create a search cursor.
IFeatureCursor featureCursor = featureClass.Search(null, enableRecycling);
comReleaser.ManageLifetime(featureCursor);
// Create a sum of each geometry's area.
IFeature feature = null;
double totalShapeArea = 0;
while ((feature = featureCursor.NextFeature()) != null)
{
IArea shapeArea = (IArea)feature.Shape;
totalShapeArea += shapeArea.Area;
}
Console.WriteLine("Total shape area: {0}", totalShapeArea);
}
}
[VB.NET]
Public Shared Sub RecyclingAppropriateExample(ByVal featureClass As IFeatureClass, ByVal enableRecycling As Boolean)
Using comReleaser As ComReleaser = New ComReleaser()
' Create a search cursor.
Dim featureCursor As IFeatureCursor = featureClass.Search(Nothing, enableRecycling)
comReleaser.ManageLifetime(featureCursor)
' Create a sum of each geometry's area.
Dim feature As IFeature = featureCursor.NextFeature()
Dim totalShapeArea As Double = 0
While Not feature Is Nothing
Dim shapeArea As IArea = CType(feature.Shape, IArea)
totalShapeArea + = shapeArea.Area
End While
Console.WriteLine("Total shape area: {0}", totalShapeArea)
End Using
End Sub
The example above was tested on a feature class in a file geodatabase containing approximately 500,000 features with the following results:
- The process' working set increased by ~4% with recycling enabled as opposed to ~48% with recycling disabled
- With recycling disabled, the method took ~2.25 times as long to run
Other similar workflows can result in an even more dramatic difference when inappropriately using non-recycling cursors, such as a working set increase of nearly 250% and an execution time twelve times longer than with recycling enabled.
Methods such as IClass.FindField and IFields.FindField are used to retrieve the position of a field in a dataset or a fields collection based on its name. Relying on FindField as opposed to hard-coded field positions is a good practice but overusing FindField can hinder performance. Consider the following example, where the "NAME" attribute is retrieved from a cursor's features:
[C#]
public static void ExcessiveFindFieldCalls(IFeatureClass featureClass)
{
using(ComReleaser comReleaser = new ComReleaser())
{
// Open a cursor on the feature class.
IFeatureCursor featureCursor = featureClass.Search(null, true);
comReleaser.ManageLifetime(featureCursor);
// Display the NAME value from each feature.
IFeature feature = null;
while ((feature = featureCursor.NextFeature()) != null)
{
Console.WriteLine(feature.get_Value(featureClass.FindField("NAME")));
}
}
}
[VB.NET]
Public Shared Sub ExcessiveFindFieldCalls(ByVal featureClass As IFeatureClass)
Using comReleaser As ComReleaser = New ComReleaser()
' Open a cursor on the feature class.
Dim featureCursor As IFeatureCursor = featureClass.Search(Nothing, True)
comReleaser.ManageLifetime(featureCursor)
' Display the NAME value from each feature.
Dim feature As IFeature = featureCursor.NextFeature()
While Not feature Is Nothing
Console.WriteLine(feature.Value(featureClass.FindField("NAME")))
feature = featureCursor.NextFeature()
End While
End Using
End Sub
Although the FindField call is not an expensive operation, when large numbers of features are involved the cost does add up. Changing the code so that the FindField result is reused will typically increase performance by 3% to 10% (or more in some cases) and requires little effort.
The following code example shows an additional good practice - checking that FindField values are not -1. If a field cannot be found, FindField will return a -1. If a value of -1 is then used as a parameter for the Value property (the get_Value and set_Value methods in C#) a descriptive error message will not be returned, as Value has no way of knowing what field the client intended to access.
[C#]
public static void SingleFindFieldCall(IFeatureClass featureClass)
{
using(ComReleaser comReleaser = new ComReleaser())
{
// Open a cursor on the feature class.
IFeatureCursor featureCursor = featureClass.Search(null, true);
comReleaser.ManageLifetime(featureCursor);
// Display the NAME value from each feature.
IFeature feature = null;
int nameIndex = featureClass.FindField("NAME");
// Make sure the FindField result is valid.
if (nameIndex == - 1)
{
throw new ArgumentException("The NAME field could not be found.");
}
while ((feature = featureCursor.NextFeature()) != null)
{
Console.WriteLine(feature.get_Value(nameIndex));
}
}
}
[VB.NET]
Public Shared Sub SingleFindFieldCall(ByVal featureClass As IFeatureClass)
Using comReleaser As ComReleaser = New ComReleaser()
' Open a cursor on the feature class.
Dim featureCursor As IFeatureCursor = featureClass.Search(Nothing, True)
comReleaser.ManageLifetime(featureCursor)
' Display the NAME value from each feature.
Dim feature As IFeature = featureCursor.NextFeature()
Dim nameIndex As Integer = featureClass.FindField("NAME")
' Make sure the FindField result is valid.
If nameIndex = -1 Then
Throw New ArgumentException("The NAME field could not be found.")
End If
While Not feature Is Nothing
Console.WriteLine(feature.Value(nameIndex))
feature = featureCursor.NextFeature()
End While
End Using
End Sub
When a cursor is created from a versioned dataset, it's bound to a specific state of the geodatabase. If the state of the geodatabase changes - for example, if the current edit operation is committed or aborted and a new edit operation is started - the cursor is no longer valid and should not be used. Performing edits on a cursor that is incorrectly scoped can cause data corruption and other unexpected behavior.
The following is an example of what to avoid. Notice that the same cursor is created in one edit operation and used to perform an insert in the following edit operation:
[C#]
public static void IncorrectCursorScoping(IWorkspace workspace, IFeatureClass
featureClass)
{
// Start an edit session and edit operation.
using(ComReleaser comReleaser = new ComReleaser())
{
IWorkspaceEdit workspaceEdit = (IWorkspaceEdit)workspace;
workspaceEdit.StartEditing(true);
workspaceEdit.StartEditOperation();
// Create a new insert cursor and feature buffer.
IFeatureCursor featureCursor = featureClass.Insert(true);
comReleaser.ManageLifetime(featureCursor);
IFeatureBuffer featureBuffer = featureClass.CreateFeatureBuffer();
comReleaser.ManageLifetime(featureBuffer);
// Insert a feature.
featureBuffer.Shape = new PointClass
{
X = 5, Y = 10
};
featureCursor.InsertFeature(featureBuffer);
// Abort the edit operation and start a new one.
workspaceEdit.AbortEditOperation();
workspaceEdit.StartEditOperation();
// Insert another feature.
featureBuffer.Shape = new PointClass
{
X = 10, Y = 5
};
featureCursor.InsertFeature(featureBuffer);
// Commit the edit operation and stop editing.
workspaceEdit.StopEditOperation();
workspaceEdit.StopEditing(true);
}
}
[VB.NET]
Public Shared Sub IncorrectCursorScoping(ByVal workspace As IWorkspace, ByVal featureClass As IFeatureClass)
' Start an edit session and edit operation.
Using comReleaser As ComReleaser = New ComReleaser()
Dim workspaceEdit As IWorkspaceEdit = CType(workspace, IWorkspaceEdit)
workspaceEdit.StartEditing(True)
workspaceEdit.StartEditOperation()
' Create a new insert cursor and feature buffer.
Dim featureCursor As IFeatureCursor = featureClass.Insert(True)
comReleaser.ManageLifetime(featureCursor)
Dim featureBuffer As IFeatureBuffer = featureClass.CreateFeatureBuffer()
comReleaser.ManageLifetime(featureBuffer)
' Insert a feature.
featureBuffer.Shape = New PointClass With {.X = 5, .Y = 10}
featureCursor.InsertFeature(featureBuffer)
' Abort the edit operation and start a new one.
workspaceEdit.AbortEditOperation()
workspaceEdit.StartEditOperation()
' Insert another feature.
featureBuffer.Shape = New PointClass With {.X = 10, .Y = 5}
featureCursor.InsertFeature(featureBuffer)
' Commit the edit operation and stop editing.
workspaceEdit.StopEditOperation()
workspaceEdit.StopEditing(True)
End Using
End Sub
Instead of the pattern used in the previous code example, create a new cursor for every edit operation. The using block indicating the lifetime of the ComReleaser provides a convenient delimiter for this kind of pattern:
[C#]
public static void CorrectCursorScoping(IWorkspace workspace, IFeatureClass
featureClass)
{
// Start an edit session and edit operation.
IWorkspaceEdit workspaceEdit = (IWorkspaceEdit)workspace;
workspaceEdit.StartEditing(true);
workspaceEdit.StartEditOperation();
// Create a new insert cursor and feature buffer.
using(ComReleaser comReleaser = new ComReleaser())
{
IFeatureCursor featureCursor = featureClass.Insert(true);
comReleaser.ManageLifetime(featureCursor);
IFeatureBuffer featureBuffer = featureClass.CreateFeatureBuffer();
comReleaser.ManageLifetime(featureBuffer);
// Insert a feature.
featureBuffer.Shape = new PointClass
{
X = 5, Y = 10
};
featureCursor.InsertFeature(featureBuffer);
}
// Abort the edit operation and start a new one.
workspaceEdit.AbortEditOperation();
workspaceEdit.StartEditOperation();
// Create another insert cursor and feature buffer.
using(ComReleaser comReleaser = new ComReleaser())
{
IFeatureCursor featureCursor = featureClass.Insert(true);
comReleaser.ManageLifetime(featureCursor);
IFeatureBuffer featureBuffer = featureClass.CreateFeatureBuffer();
comReleaser.ManageLifetime(featureBuffer);
// Insert another feature.
featureBuffer.Shape = new PointClass
{
X = 10, Y = 5
};
featureCursor.InsertFeature(featureBuffer);
}
// Commit the edit operation and stop editing.
workspaceEdit.StopEditOperation();
workspaceEdit.StopEditing(true);
}
[VB.NET]
Public Shared Sub CorrectCursorScoping(ByVal workspace As IWorkspace, ByVal featureClass As IFeatureClass)
' Start an edit session and edit operation.
Dim workspaceEdit As IWorkspaceEdit = CType(workspace, IWorkspaceEdit)
workspaceEdit.StartEditing(True)
workspaceEdit.StartEditOperation()
' Create a new insert cursor and feature buffer.
Using comReleaser As ComReleaser = New ComReleaser()
Dim featureCursor As IFeatureCursor = featureClass.Insert(True)
comReleaser.ManageLifetime(featureCursor)
Dim featureBuffer As IFeatureBuffer = featureClass.CreateFeatureBuffer()
comReleaser.ManageLifetime(featureBuffer)
' Insert a feature.
featureBuffer.Shape = New PointClass With {.X = 5, .Y = 10}
featureCursor.InsertFeature(featureBuffer)
End Using
' Abort the edit operation and start a new one.
workspaceEdit.AbortEditOperation()
workspaceEdit.StartEditOperation()
' Create another insert cursor and feature buffer.
Using comReleaser As ComReleaser = New ComReleaser()
Dim featureCursor As IFeatureCursor = featureClass.Insert(True)
comReleaser.ManageLifetime(featureCursor)
Dim featureBuffer As IFeatureBuffer = featureClass.CreateFeatureBuffer()
comReleaser.ManageLifetime(featureBuffer)
' Insert another feature.
featureBuffer.Shape = New PointClass With {.X = 10, .Y = 5}
featureCursor.InsertFeature(featureBuffer)
End Using
' Commit the edit operation and stop editing.
workspaceEdit.StopEditOperation()
workspaceEdit.StopEditing(True)
End Sub
Data definition language (DDL) commands are database commands that modify the schema of a database. Examples include creating new tables, adding a new field to a table, or dropping an index. Methods that trigger DDL commands, such as IFeatureWorkspace.CreateTable or IClass.AddField, should never be called inside of an edit session, because DDL commands will commit any transactions that are currently open, making it impossible to rollback any unwanted edits should an error occur.
This recommendation also extends to geodatabase schema modification that isn't true DDL from a database perspective, such as modifying a domain, because these types of operations explicitly commit their changes. A real-world example of this is a custom editing application that adds new values to a coded value domain based on a user's edits, then fails unexpectedly when the application tries to commit the edits. The recommended approach in cases like these is to maintain a list of values that the user has provided, then add them once the edit session has been stopped.
The Geodatabase API exposes several events that allow developers to apply custom behavior when Store is called on a method, such as IObjectClassEvents.OnCreate and IRelatedObjectClassEvents.RelatedObjectCreated. Developers implementing class extensions or event handlers that define custom behavior through these methods and others like them should ensure that Store is not called again on the row that triggered the event, even if the custom behavior caused the row to be modified. Calling Store on the object again is simply triggered the event model from within the model itself, leading to unexpected behavior - in some cases this will result in infinite recursion, causing an application to hang, while in others errors will be returned with messages that may be difficult to interpret.
Below is a simple "timestamp" example that is intended to maintain the current user's name on features being created, but will just produce different varieties of errors depending on the data source:
[C#]
private static void EventHandlerInitialization(IFeatureClass featureClass)
{
IObjectClassEvents_Event objectClassEvents = (IObjectClassEvents_Event)
featureClass;
objectClassEvents.OnCreate += new IObjectClassEvents_OnCreateEventHandler
(OnCreateHandler);
}
private static void OnCreateHandler(IObject obj)
{
obj.set_Value(NAME_INDEX, Environment.UserName);
obj.Store(); // Do NOT do this!
}
[VB.NET]
Private Shared Sub EventHandlerInitialization(ByVal featureClass As IFeatureClass)
Dim objectClassEvents As IObjectClassEvents_Event = CType(featureClass, IObjectClassEvents_Event)
AddHandler objectClassEvents.OnCreate, AddressOf OnCreateHandler
End Sub
Private Shared Sub OnCreateHandler(ByVal obj As IObject)
obj.Value(NAME_INDEX) = Environment.UserName
obj.Store() ' Do NOT do this!
End Sub
The IFeatureClass interface exposes two similar methods for retrieving features by their Object IDs - GetFeature and GetFeatures. The former will retrieve a single feature and takes an integer parameter, while the second will create a cursor that returns the features specified in an integer array parameter (it also has a parameter that specifies whether the cursor will be recycling).
For performance purposes, any time more than one feature is being retrieved using a known Object ID the GetFeatures method should always be used. Compare the two code examples:
[C#]
private static void GetFeatureExample(IFeatureClass featureClass, int[] oidList)
{
int nameFieldIndex = featureClass.FindField("NAME");
foreach (int oid in oidList)
{
IFeature feature = featureClass.GetFeature(oid);
Console.WriteLine("NAME: {0}", feature.get_Value(nameFieldIndex));
}
}
private static void GetFeaturesExample(IFeatureClass featureClass, int[]
oidList)
{
int nameFieldIndex = featureClass.FindField("FIPSSTCO");
using(ComReleaser comReleaser = new ComReleaser())
{
IGeoDatabaseBridge geodatabaseBridge = new GeoDatabaseHelperClass();
IFeatureCursor featureCursor = geodatabaseBridge.GetFeatures(featureClass,
ref oidList, true);
comReleaser.ManageLifetime(featureCursor);
IFeature feature = null;
while ((feature = featureCursor.NextFeature()) != null)
{
Console.WriteLine("NAME: {0}", feature.get_Value(nameFieldIndex));
}
}
}
[VB.NET]
Private Shared Sub GetFeatureExample(ByVal featureClass As IFeatureClass, ByVal oidList As Integer())
Dim nameFieldIndex As Integer = featureClass.FindField("NAME")
For Each oid As Integer In oidList
Dim feature As IFeature = featureClass.GetFeature(oid)
Console.WriteLine("NAME: {0}", feature.Value(nameFieldIndex))
Next
End Sub
Private Shared Sub GetFeaturesExample(ByVal featureClass As IFeatureClass, ByVal oidList As Integer())
Dim nameFieldIndex As Integer = featureClass.FindField("NAME")
Using comReleaser As ComReleaser = New ComReleaser()
Dim geodatabaseBridge As IGeoDatabaseBridge = New GeoDatabaseHelper()
Dim featureCursor As IFeatureCursor = geodatabaseBridge.GetFeatures(featureClass, oidList, True)
comReleaser.ManageLifetime(featureCursor)
Dim feature As IFeature = featureCursor.NextFeature()
While Not feature Is Nothing
Console.WriteLine("NAME: {0}", feature.Value(nameFieldIndex))
feature = featureCursor.NextFeature()
End While
End Using
End Sub
The code examples above have the same level of performance if a single feature is being requested, but the GetFeatures example will outperform the GetFeature example on as few as two features (especially with remote databases), and the difference between the two grows as more features are requested. With one hundred features, the GetFeature example typically requires 10-12 times as much time to run, while with one thousand features it will often take up to 20 times as long.
The careless reuse of variables can cause two types of complications when working with the Geodatabase API. The first type of problem is most commonly seen when creating collections like sets of fields; see the following code example, which was intended to create a set of fields containing an Object ID field and a string field:
[C#]
private static IFields FieldSetCreation()
{
// Create a new field collection and a field.
IFields fields = new FieldsClass();
IFieldsEdit fieldsEdit = (IFieldsEdit)fields;
IField field = new FieldClass();
IFieldEdit fieldEdit = (IFieldEdit)field;
// Add an ObjectID field.
fieldEdit.Name_2 = "OBJECTID";
fieldEdit.Type_2 = esriFieldType.esriFieldTypeOID;
fieldsEdit.AddField(field);
// Add a text field.
fieldEdit.Name_2 = "NAME";
fieldEdit.Type_2 = esriFieldType.esriFieldTypeString;
fieldsEdit.AddField(field);
return fields;
}
[VB.NET]
Private Shared Function FieldSetCreation() As IFields
' Create a new field collection and a field.
Dim fields As IFields = New FieldsClass()
Dim fieldsEdit As IFieldsEdit = CType(fields, IFieldsEdit)
Dim field As IField = New FieldClass()
Dim fieldEdit As IFieldEdit = CType(field, IFieldEdit)
' Add an ObjectID field.
fieldEdit.Name_2 = "OBJECTID"
fieldEdit.Type_2 = esriFieldType.esriFieldTypeOID
fieldsEdit.AddField(field)
' Add a text field.
fieldEdit.Name_2 = "NAME"
fieldEdit.Type_2 = esriFieldType.esriFieldTypeString
fieldsEdit.AddField(field)
Return fields
End Function
The reason why this code does not work as anticipated may not be immediately visible, and the error message returned when the resulting field set is used to create a table may not help a great deal (it will be something to the effect of duplicate fields existing in the table). What is actually happening is the final field set contains two fields - two identical string fields. Since the "field" and "fieldEdit" variables still reference the Object ID field that has been added, that field object is being modified and then added a second time to the collection. This could be avoided using two different approaches:
- Reassign the field and fieldEdit variables to a newly created field object after each field is added
- Use a separate set of variables for each field that will be added to the collection, i.e. "oidField" and "oidFieldEdit"
The second type of problem that results from careless reuse of variables is losing all references to objects that should be explicitly released using the ComReleaser class or the Marshal.ReleaseComObject method. Consider the following code example:
[C#]
private static void CursorReassignment(IFeatureClass featureClass, IQueryFilter
queryFilter)
{
// Execute a query...
IFeatureCursor featureCursor = featureClass.Search(queryFilter, true);
IFeature feature = null;
while ((feature = featureCursor.NextFeature()) != null)
{
// Do something with the feature...
}
// Re-execute the query...
featureCursor = featureClass.Search(queryFilter, true);
feature = null;
while ((feature = featureCursor.NextFeature()) != null)
{
// Do something with the feature...
}
// Release the cursor.
Marshal.ReleaseComObject(featureCursor);
}
[VB.NET]
Private Shared Sub CursorReassignment(ByVal featureClass As IFeatureClass, ByVal queryFilter As IQueryFilter)
' Execute a query...
Dim featureCursor As IFeatureCursor = featureClass.Search(queryFilter, True)
Dim feature As IFeature = featureCursor.NextFeature()
While Not feature Is Nothing
' Do something with the feature...
feature = featureCursor.NextFeature()
End While
' Re-execute the query...
featureCursor = featureClass.Search(queryFilter, True)
feature = featureCursor.NextFeature()
While Not feature Is Nothing
' Do something with the feature...
feature = featureCursor.NextFeature()
End While
' Release the cursor.
Marshal.ReleaseComObject(featureCursor)
End Sub
The problem that occurs in this kind of situation is that only the second cursor object that was instantiated is actually being released. Since the only reference to the first was lost, the first cursor is now dependent on being released by non-deterministic garbage collection. The same problem can also occur when the ComReleaser class is used; lifetime management is object-specific, not variable-specific. For example, in the following example, only the first cursor is properly managed:
[C#]
private static void CursorReassignment(IFeatureClass featureClass, IQueryFilter
queryFilter)
{
using(ComReleaser comReleaser = new ComReleaser())
{
// Execute a query...
IFeatureCursor featureCursor = featureClass.Search(queryFilter, true);
comReleaser.ManageLifetime(featureCursor);
IFeature feature = null;
while ((feature = featureCursor.NextFeature()) != null)
{
// Do something with the feature...
}
// Re-execute the query...
featureCursor = featureClass.Search(queryFilter, true);
feature = null;
while ((feature = featureCursor.NextFeature()) != null)
{
// Do something with the feature...
}
}
}
[VB.NET]
Private Shared Sub CursorReassignment(ByVal featureClass As IFeatureClass, ByVal queryFilter As IQueryFilter)
Using comReleaser As ComReleaser = New ComReleaser
' Execute a query...
Dim featureCursor As IFeatureCursor = featureClass.Search(queryFilter, True)
comReleaser.ManageLifetime(featureCursor)
Dim feature As IFeature = featureCursor.NextFeature()
While Not feature Is Nothing
' Do something with the feature...
feature = featureCursor.NextFeature()
End While
' Re-execute the query...
featureCursor = featureClass.Search(queryFilter, True)
feature = featureCursor.NextFeature()
While Not feature Is Nothing
' Do something with the feature...
feature = featureCursor.NextFeature()
End While
End Using
End Sub
Notification (also known as messaging) is a property of relationship classes that define in which direction messages are sent between the two object classes participating in the relationship class. There are four types of notification:
- None - This is typical for simple relationships
- Forward - This is typical for composite relationships
- Backward
- Both (bi-directional)
These messages ensure the proper behavior of composite relationships, feature-linked annotation classes, and many custom class extensions. This behavior does come at a price, however - edits and inserts to datasets that trigger notification is noticeably slower than the same operation on datasets that do not trigger any notification.
For inserts, this performance hit can be mitigated by ensuring that all notified classes are opened prior to any inserts taking place. The following code example is based on a schema where a parcels feature class participates in a composite relationship class with an owners table, an inserts are being made to the feature class:
[C#]
public static void NotifiedClassEditsExample(IWorkspace workspace)
{
// Open the class that will be edited.
IFeatureWorkspace featureWorkspace = (IFeatureWorkspace)workspace;
IFeatureClass featureClass = featureWorkspace.OpenFeatureClass("PARCELS");
ITable table = featureWorkspace.OpenTable("OWNERS");
// Begin an edit session and operation.
IWorkspaceEdit workspaceEdit = (IWorkspaceEdit)workspace;
workspaceEdit.StartEditing(true);
workspaceEdit.StartEditOperation();
// Create a search cursor.
using(ComReleaser comReleaser = new ComReleaser())
{
IFeatureCursor featureCursor = featureClass.Insert(true);
comReleaser.ManageLifetime(featureCursor);
IFeatureBuffer featureBuffer = featureClass.CreateFeatureBuffer();
comReleaser.ManageLifetime(featureBuffer);
for (int i = 0; i < 1000; i++)
{
featureBuffer.Shape = CreateRandomPolygon();
featureCursor.InsertFeature(featureBuffer);
}
featureCursor.Flush();
}
// Commit the edits.
workspaceEdit.AbortEditOperation();
workspaceEdit.StopEditing(false);
}
[VB.NET]
Public Shared Sub NotifiedClassEditsExample(ByVal workspace As IWorkspace)
' Open the class that will be edited.
Dim featureWorkspace As IFeatureWorkspace = CType(workspace, IFeatureWorkspace)
Dim featureClass As IFeatureClass = featureWorkspace.OpenFeatureClass("PARCELS")
Dim Table As ITable = featureWorkspace.OpenTable("OWNERS")
' Begin an edit session and operation.
Dim workspaceEdit As IWorkspaceEdit = CType(workspace, IWorkspaceEdit)
workspaceEdit.StartEditing(True)
workspaceEdit.StartEditOperation()
' Create a search cursor.
Using comReleaser As ComReleaser = New ComReleaser()
Dim featureCursor As IFeatureCursor = featureClass.Insert(True)
comReleaser.ManageLifetime(featureCursor)
Dim featureBuffer As IFeatureBuffer = featureClass.CreateFeatureBuffer()
comReleaser.ManageLifetime(featureBuffer)
For i As Integer = 0 To 1000
featureBuffer.Shape = CreateRandomPolygon()
featureCursor.InsertFeature(featureBuffer)
Next i
featureCursor.Flush()
End Using
' Commit the edits.
workspaceEdit.AbortEditOperation()
workspaceEdit.StopEditing(False)
End Sub
The performance benefits of ensuring the notified class has been opened in this scenario is extremely significant. In the case above, where one thousand features are inserted, failing to open the notified class will typically cause an application to run for 10 to 15 times as long as it would with the notified class open. This is especially significant when a class triggers notification to multiple classes, as this factor will be multiplied by the number of classes that are being notified (i.e. a 50-75 times increase in running time if five unopened classes are being notified).
Every type of geodatabase object - datasets, domains, fields, etc. - has a corresponding class in the API. Developers should be aware that these classes fall into two categories of behaviors:
- Those that automatically persist schema changes in the geodatabase, i.e. tables
- Those that do not, i.e. fields, domains, indexes
A classic example of this are the methods IClass.AddField and IFieldsEdit.AddField. When the former is called, the API adds a field to the database table. When the latter is called, a field is added to the field collection in memory but no change is made to the actual table. Many developers have discovered the hard way that opening a table, getting a fields collection and adding a new field to it is not the recommended workflow.
Other invalid workflows include:
- Modifying fields that have already been created in the geodatabase using the IFieldEdit interface
- Modifying index collections that have already been created in the geodatabase using the IIndexesEdit interface
- Modifying indexes that have already been created in the geodatabase using the IIndexEdit interface
Another similar workflow is retrieving a domain from a workspace and making modifications to it; for example, adding a new code to a coded value domain. While these changes aren't automatically persisted in the geodatabase, IWorkspaceDomains2.AlterDomain can be called to overwrite the persisted domain with the modified object.