3.6 不小心重用变量
在使用Geodatabase API时,不小心重用变量会导致两种类型的复杂情况。
The careless reuse of variables can cause two types of complications when working with the Geodatabase API.
3.6.1 创建字段或字段集
第一种复杂情况在创建集合(如字段集)时最常见。请参阅以下代码示例,该示例旨在创建一组包含ObjectID字段和字符串字段的字段:【The first type of complication is most commonly seen when creating collections, such as sets of fields. See the following code example, which was intended to create a set of fields containing an ObjectID field and a string field:】
private static IFields FieldSetCreation() { // Create a 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; }
此代码不能按预期工作的原因可能不是很明显,并且在使用结果字段集创建表时返回的错误消息可能没有多大帮助(这将是表中存在重复字段的结果)。实际发生的是最终的字段集包含两个字段(两个相同的字符串字段)。由于“field”和“fieldEdit”变量仍然引用已添加的ObjectID字段,因此正在修改该字段对象,然后再次将其添加到集合中。这可以使用以下两种不同的方法来避免:
在添加每个字段后,将字段和fieldEdit变量重新分配给新创建的字段对象。
为要添加到集合中的每个字段使用一组单独的变量,即“oidField”和“oidFieldEdit”
The reason this code does not work as anticipated might not be immediately apparent, and the error message returned when the resulting field set is used to create a table, might not help a much (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 ObjectID field that has been added, that field object is being modified, then added a second time to the collection. This can be avoided using the following 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, that is, "oidField" and "oidFieldEdit"
正确的玩法:
int nameIndex = featureClass.FindField("NAME"); if(nameIndex == -1) { IField pField = new FieldClass(); IFieldEdit pFieldEdit = pField as IFieldEdit; pFieldEdit.Name_2 = "NAME"; pFieldEdit.Type_2 = esriFieldType.esriFieldTypeString; pFieldEdit.Length_2 = 200; featureClass.AddField(pFieldEdit); }
3.6.2 未显式释放对象
由于不小心重用变量而导致的第二种复杂情况是,丢失对应该使用ComReleaser类或Marshal.ReleaseComObject方法显式释放的对象的所有引用。[The second type of complication 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.]
请考虑以下代码示例:
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); }
在这种情况下出现的问题是,实际上只有第二个被实例化的游标对象被释放。由于对第一个指针的唯一引用丢失,第一个指针现在依赖于由不确定的垃圾收集释放。使用ComReleaser类时也可能出现相同的问题。生命周期管理是对象特定的,而不是变量特定的。【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 code example, only the first cursor is properly managed:】
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... } } }
3.7 插入或关系类通知
通知(也称为消息传递)是关系类的一个属性,用于定义在参与关系类的两个对象类之间发送的方向消息。以下是四种通知:
- 不是简单关系的典型
复合关系的前向典型值
向后
两者(双向)
Notification (also known as messaging) is a property of relationship classes that define which direction messages are sent between the two object classes participating in the relationship class. The following are the four types of notification:
None—Typical for simple relationships
Forward—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 before 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, and inserts are being made to the feature class:
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); }
在这种情况下,确保 已通知的类已被打开 的性能好处,是非常显著的。在前一种情况下,如果插入了1000个特性,如果未能打开通知类,则通常会导致应用程序运行10-15倍于打开通知类的时间。当一个类触发对多个类的通知时,这一点尤其重要,因为这个系数乘以被通知的类的数量(即,如果通知了五个未打开的类,则运行时间将增加50-75倍)。
The performance benefits of ensuring the notified class has been opened in this scenario is extremely significant. In the preceding case, where 1,000 features are inserted, failing to open the notified class typically causes an application to run for 10–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 is multiplied by the number of classes that are being notified (that is, a 50–75 times increase in running time if five unopened classes are being notified).
3.8 Modifying schema objects
修改架构对象
每种类型的geodatabase对象数据集、域、字段等在API中都有相应的类。开发人员应注意,这些类分为以下两类行为:在geodatabase中自动持久化模式更改的那些,即表那些没有的,即字段、域、索引
Every type of geodatabase object—datasets, domains, fields, and so on—has a corresponding class in the API. Developers should be aware that these classes fall into two categories of the following behaviors:
Those that automatically persist schema changes in the geodatabase, that is, tables
Those that do not, that is, fields, domains, indexes
一个典型的例子是IClass.AddField和IFieldsEdit.AddField方法。调用前者时,API会向数据库表中添加一个字段。当调用后者时,将向内存中的字段集合添加一个字段,但不会更改实际的表。许多开发人员发现,打开一个表、获取一个字段集合并向其中添加一个新字段并不是正确的工作流,这是一种困难的方法。
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 correct workflow.
其他无效工作流包括:
使用IFieldEdit接口修改已经在geodatabase中创建的字段
使用IIndexesEdit接口修改已在geodatabase中创建的索引集合
使用IIndexEdit接口修改已在geodatabase中创建的索引
Other invalid workflows include the following:
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
另一个类似的工作流是从工作区检索域并对其进行修改,例如,向编码值域添加新代码。虽然这些更改不会自动持久化在geodatabase中,但可以调用IWorkspaceDomains2.AlterDomain以使用修改的对象覆盖持久化的域。
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 are not automatically persisted in the geodatabase, IWorkspaceDomains2.AlterDomain can be called to overwrite the persisted domain with the modified object.
4 空间查询优化
本部分内容来源自ArcEngine空间查询优化
4.1 两个图层,点层和线层,查相交
public void Method_A(IFeatureLayer pSourceFeatureLayer, IFeatureLayer pTargetFeatureLayer) { IQueryFilter pQueryFilter = new QueryFilter(); pQueryFilter.AddField("Shape"); // 源图层 IFeatureClass pSourceFeatureClass = pSourceFeatureLayer.FeatureClass; IFeatureCursor pSourceFeatureCursor = pSourceFeatureClass.Search(pQueryFilter, true); IFeature pSourceFeature = pSourceFeatureCursor.NextFeature(); if (pSourceFeature == null) { return; } // 目标图层 IFeatureSelection pTargetFeatureSelection = pTargetFeatureLayer as IFeatureSelection; ISpatialFilter pSpatialFilter = new SpatialFilter(); while (pSourceFeature != null) { pSpatialFilter.Geometry = pSourceFeature.ShapeCopy; pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects; pTargetFeatureSelection.SelectFeatures(pSpatialFilter, esriSelectionResultEnum.esriSelectionResultAdd, false); pSourceFeature = pSourceFeatureCursor.NextFeature(); } Marshal.ReleaseComObject(pSourceFeatureCursor); // 刷新视图 axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, null, null); }
20毫秒!!!该方法的缺点:一旦数据量较大时,遍历次数较多,时间效率就会较低。我们可以回想一下,传统关系型数据库怎么优化查询?
4.2 创建空间索引优化
除了优化SQL,创建索引也是一个较好的方法,在ArcEngne中也同样可以创建空间索引,代码如下
public void Method_B(IFeatureLayer pSourceFeatureLayer, IFeatureLayer pTargetFeatureLayer) { IFeatureClass pSourceFeatureClass = pSourceFeatureLayer.FeatureClass; IGeoDataset pGeoDataset = pSourceFeatureClass as IGeoDataset; ISpatialReference pSpatialReference = pGeoDataset.SpatialReference; // 实体几何 IGeometryBag pGeometryBag = new GeometryBag() as IGeometryBag; pGeometryBag.SpatialReference = pSpatialReference; IGeometryCollection pGeometryCollection = pGeometryBag as IGeometryCollection; // 要素游标 IFeatureCursor pSourceFeatureCursor = pSourceFeatureClass.Search(null, true); IFeature pSourceFeature = pSourceFeatureCursor.NextFeature(); if (pSourceFeature == null) { return; } // 添加实体 object missing = Type.Missing; while (pSourceFeature != null) { pGeometryCollection.AddGeometry(pSourceFeature.ShapeCopy, ref missing, ref missing); pSourceFeature = pSourceFeatureCursor.NextFeature(); } Marshal.ReleaseComObject(pSourceFeatureCursor); // 创建空间索引 ISpatialIndex pSpatialIndex = pGeometryBag as ISpatialIndex; pSpatialIndex.AllowIndexing = true; pSpatialIndex.Invalidate(); // 创建空间过滤器 ISpatialFilter pSpatialFilter = new SpatialFilter(); pSpatialFilter.Geometry = pGeometryBag; pSpatialFilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects; // 刷新视图 IFeatureSelection pTargetFeatureSelection = pTargetFeatureLayer as IFeatureSelection; pTargetFeatureSelection.SelectFeatures(pSpatialFilter, esriSelectionResultEnum.esriSelectionResultAdd, false); axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, null, null); }
15毫秒!!!这种方法已经能够满足大部分需求,但还有没有更快的方法?
4.3 IQueryByLayer接口
ArcEngine有一个IQueryByLayer接口,这个接口很有意思,首先贴上代码:
public void Method_C(IFeatureLayer pSourceFeatureLayer, IFeatureLayer pTargetFeatureLayer) { IQueryByLayer pQueryByLayer = new QueryByLayer(); pQueryByLayer.FromLayer = pTargetFeatureLayer; pQueryByLayer.ByLayer = pSourceFeatureLayer; pQueryByLayer.LayerSelectionMethod = esriLayerSelectionMethod.esriLayerSelectIntersect; pQueryByLayer.UseSelectedFeatures = false; // 刷新视图 IFeatureSelection pFeatureSelection = pTargetFeatureLayer as IFeatureSelection; ISelectionSet pSelectionSet = pQueryByLayer.Select(); pFeatureSelection.SelectionSet = pSelectionSet; axMapControl1.ActiveView.PartialRefresh(esriViewDrawPhase.esriViewGeoSelection, null, null); }
接口介绍
// 摘要: // The type of selection method to be performed. [DispId(1610678275)] double BufferDistance { set; } // // 摘要: // The buffer units. [DispId(1610678276)] esriUnits BufferUnits { set; } // // 摘要: // The layer features will be selected from. [DispId(1610678273)] IFeatureLayer ByLayer { set; } // // 摘要: // Provides access to the methods and properties of QueryByLayer. [DispId(1610678272)] IFeatureLayer FromLayer { set; } // // 摘要: // The input layer that contains features to base the selection on. [DispId(1610678274)] esriLayerSelectionMethod LayerSelectionMethod { set; } // // 摘要: // The result type of the selection where it can be specified that the selection // adds to a current selection etc. [DispId(1610678278)] esriSelectionResultEnum ResultType { set; } // // 摘要: // Indicates whether selected features will be used. [DispId(1610678277)] bool UseSelectedFeatures { set; } // 摘要: // Selects the features based on the input parameters and returns a selection // set. ISelectionSet Select();
我们再来看一下ArcMap中的空间查询界面,如下图:
我们发现,IQueryByLayer接口中的BuferDistance对应“应用搜索距离”,BufferUnits对应搜索距离的单位,由于ArcGIS Desktop和ArcEngine都是基于ArcObjects,所以Desktop中的空间查询也是基于IQueryByLayer接口。