基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化

目录

1 查询接口函数解析

1.1 IFeatureClass.Search()

1.1.1 函数说明

1.1.2 依据点坐标查找图层中的第一个要素

1.2 IFeatureClass.Update()

1.2.1 函数说明

1.2.2 游标要素更新

1.3 IFeatureClass.Insert()

1.3.1 函数说明

1.3.2 示例

2 资源的释放

2.1 使用Marsh.ReleaseComObject进行查询游标的释放

2.2 分析

3 Geadatebse API使用最佳实践

3.1 对recycling的理解

3.2 存储FindField的结果

3.3 DDL命令不能在编辑时使用

3.4 Calling Store inside of Store-triggered events

3.5 获取要素

3.6 不小心重用变量

3.6.1 创建字段或字段集

3.6.2 未显式释放对象

3.7 插入或关系类通知

3.8 Modifying schema objects

4 空间查询优化

4.1 两个图层,点层和线层,查相交

4.2 创建空间索引优化

4.3 IQueryByLayer接口


注:文章第四部分搬运自ArcEngine空间查询优化;版权归原作者所有,特此声明

1 查询接口函数解析

1.1 IFeatureClass.Search()

1.1.1 函数说明

[C#]public IFeatureCursor Search ( IQueryFilterfilter, boolRecycling);

Search将返回一个满足IQueryFilter 设定的属性查询或空间查询条件的IFeatureCursor 对象;如果该参数为null,则返回要素类中的所有要素。参数recycling 控制行对象的分配行为,循环光标在提取单个要素目标都会重新进行初始化,并采用只读优化;在多次调用游标的NextFeature时,保持对要素的引用是非法的;通过回收游标返回的要素是不可修改的,不可回收游标在每个对象提取时,返回的是单独的对象,可以被多种行为的修改和存储。

The recycling parameter controls row object allocation behavior. Recycling cursors rehydrate a single feature object on each fetch and can be used to optimize read-only access, for example, when drawing. It is illegal to maintain a reference on a feature object returned by a recycling cursor across multiple calls to NextFeature on the cursor. Features returned by a recycling cursor should not be modified. Non-recycling cursors return a separate feature object on each fetch. The features returned by a non-recycling cursor may be modified and stored with polymorphic behavior.

The Geodatabase guarantees "unique instance semantics" on non-recycling feature objects fetched during an edit session. In other words, if the feature retrieved by a search cursor has already been instantiated and is being referenced by the calling application, then a reference to the existing feature object is returned.

Non-recycling feature cursors returned from the Search method *MUST* be used when copying features from the cursor into an insert cursor of another class.  This is because a recycling cursor reuses the same geometry and under some circumstances all of the features inserted into the insert cursor may have the same geometry.  Using a non-recycling cursor ensures that each geometry is unique.

1.1.2 依据点坐标查找图层中的第一个要素

  • The code in this document requires the following References added to the Visual Studio project:
  • ESRI.ArcGIS.Carto
  • ESRI.ArcGIS.Geodatabase
  • ESRI.ArcGIS.Geometry
  • ESRI.ArcGIS.System
///Finds the first feature in a GeoFeature layer by supplying an point.  The point could come from a mouse click in the map.
///
///A System.Double that is the number of map units to search. Example: 25
///An IPoint interface in map units where the user clicked on the map
///An ILayer interface to search upon
///An IActiveView interface
/// 
///An IFeature interface that is the first feature found in the GeoFeatureLayer.
/// 
///
public ESRI.ArcGIS.Geodatabase.IFeature GetFirstFeatureFromPointSearchInGeoFeatureLayer(
 System.Double searchTolerance,
 ESRI.ArcGIS.Geometry.IPoint point,
 ESRI.ArcGIS.Carto.IGeoFeatureLayer geoFeatureLayer,
 ESRI.ArcGIS.Carto.IActiveView activeView)
{
  if (searchTolerance < 0 || point == null || geoFeatureLayer == null || activeView == null)
  {
    return null;
  }

  ESRI.ArcGIS.Carto.IMap map = activeView.FocusMap; 

  // Expand the points envelope to give better search results    
  ESRI.ArcGIS.Geometry.IEnvelope envelope = point.Envelope;
  envelope.Expand(searchTolerance, searchTolerance, false);

  ESRI.ArcGIS.Geodatabase.IFeatureClass featureClass = geoFeatureLayer.FeatureClass;
  System.String shapeFieldName = featureClass.ShapeFieldName;

  // Create a new spatial filter and use the new envelope as the geometry    
  ESRI.ArcGIS.Geodatabase.ISpatialFilter spatialFilter = new ESRI.ArcGIS.Geodatabase.SpatialFilterClass();
  spatialFilter.Geometry = envelope;
  spatialFilter.SpatialRel = ESRI.ArcGIS.Geodatabase.esriSpatialRelEnum.esriSpatialRelEnvelopeIntersects;
  spatialFilter.set_OutputSpatialReference(shapeFieldName, map.SpatialReference);
  spatialFilter.GeometryField = shapeFieldName;

  // Do the search
  ESRI.ArcGIS.Geodatabase.IFeatureCursor featureCursor = featureClass.Search(spatialFilter, false);

  // Get the first feature
  ESRI.ArcGIS.Geodatabase.IFeature feature = featureCursor.NextFeature();
  if (!(feature == null))
  {
    return feature;
  }
  else
  {
      return null;
  }
}

1.2 IFeatureClass.Update()

1.2.1 函数说明

[C#]public IFeatureCursor Update ( IQueryFilterfilter, boolRecycling);

配合更新游标使用,通过过滤器filter进行属性或空间查询,如果选择一组特定的查询要素对其进行更新,则使用更新游标会比逐个要素更新更有效率,其更新是通过当前游标的位置执行。更新游标可作为带有多态行为(polymorphic behavior)要素子类的实例。更新游标即可在编辑会话一起使用,也可以单独使用;当在编辑会话内部使用更新游标时,这些变化知道编辑会话被保存才会提交到基础表中;网络要素类、拓扑要素类、参与组合关系或其他信息关系的要素类,只能通过编辑会话更新。如果你尝试在编辑会话之外使用更新游标,它将会失败;除此之外,编辑参与拓扑或几何网络的的要素必须要在编辑会话内。当在编辑会话中使用游标时,必须将其作用域设置为编辑操作;也就是说,游标可在编辑操作开始之后创建,且在编辑操作停止或中止之后不应再被使用。

Update opens an update cursor on the features specified by an attribute and/or spatial query as specified by the filter parameter. If a number of features selected by a particular query are to be updated and each feature is to be updated to a separate value then the update cursor is faster than doing an individual feature level update for each feature. The update is performed on the current 'cursor position'.Update cursors can be used on instances of Feature subclasses (such as network features), with guaranteed polymorphic behavior. Update cursors can be used either inside or outside of an edit session. If used inside an edit session, the changes are not committed to the base table until the edit session is saved. Network feature classes, Topology feature classes, feature classes that participate in composite relationships or other relationships with messaging may only be updated within an edit session. If you attempt to use an update cursor on one of these classes outside of an edit session, it will fail.  In addition, edits to features that participate in a Topology or Geometric Network must be bracketed within an edit operation.When using cursors within an edit session, they should always be scoped to edit operations. In other words, a cursor should be created after an edit operation has begun and should not be used once that edit operation has been stopped or aborted.

1.2.2 游标要素更新

public void UseUpdateCursor(IFeatureClass featureClass)
{
    // Restrict the number of features to be updated.
    IQueryFilter queryFilter = new QueryFilterClass();
    queryFilter.WhereClause = "NAME = 'Highway 104'";
    queryFilter.SubFields = "TYPE";

    // Use IFeatureClass.Update to populate IFeatureCursor.
    IFeatureCursor updateCursor = featureClass.Update(queryFilter, false);

    int typeFieldIndex = featureClass.FindField("TYPE");
    IFeature feature = null;
    try
    {
        while ((feature = updateCursor.NextFeature()) != null)
        {
            feature.set_Value(typeFieldIndex, "Toll Highway");
            updateCursor.UpdateFeature(feature);
        }
    }
    catch (COMException comExc)
    {
        // Handle any errors that might occur on NextFeature().
    }

    // If the cursor is no longer needed, release it.
    Marshal.ReleaseComObject(updateCursor);
}

1.3 IFeatureClass.Insert()

1.3.1 函数说明

[C#]public IFeatureCursor Insert (bool useBuffering);

插入要素

1.3.2 示例

示例1:使用插入游标插入要素

public static void InsertFeaturesUsingCursor(IFeatureClass featureClass, List <
    IGeometry > geometryList)
{
    using(ComReleaser comReleaser = new ComReleaser())
    {
        // Create a feature buffer.
        IFeatureBuffer featureBuffer = featureClass.CreateFeatureBuffer();
        comReleaser.ManageLifetime(featureBuffer);

        // Create an insert cursor.
        IFeatureCursor insertCursor = featureClass.Insert(true);
        comReleaser.ManageLifetime(insertCursor);

        // All of the features to be created are classified as Primary Highways.
        int typeFieldIndex = featureClass.FindField("TYPE");
        featureBuffer.set_Value(typeFieldIndex, "Primary Highway");
        foreach (IGeometry geometry in geometryList)
        {
            // Set the feature buffer's shape and insert it.
            featureBuffer.Shape = geometry;
            insertCursor.InsertFeature(featureBuffer);
        }

        // Flush the buffer to the geodatabase.
        insertCursor.Flush();
    }
}

示例2:使用仅加载模式插入要素

public static void LoadOnlyModeInsert(IFeatureClass featureClass, List < IGeometry >
    geometryList)
{
    // Cast the feature class to the IFeatureClassLoad interface.
    IFeatureClassLoad featureClassLoad = (IFeatureClassLoad)featureClass;

    // Acquire an exclusive schema lock for the class.
    ISchemaLock schemaLock = (ISchemaLock)featureClass;
    try
    {
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriExclusiveSchemaLock);

        // Enable load-only mode on the feature class.
        featureClassLoad.LoadOnlyMode = true;
        using(ComReleaser comReleaser = new ComReleaser())
        {
            // Create the feature buffer.
            IFeatureBuffer featureBuffer = featureClass.CreateFeatureBuffer();
            comReleaser.ManageLifetime(featureBuffer);

            // Create an insert cursor.
            IFeatureCursor insertCursor = featureClass.Insert(true);
            comReleaser.ManageLifetime(insertCursor);

            // All of the features to be created are classified as Primary Highways.
            int typeFieldIndex = featureClass.FindField("TYPE");
            featureBuffer.set_Value(typeFieldIndex, "Primary Highway");

            foreach (IGeometry geometry in geometryList)
            {
                // Set the feature buffer's shape and insert it.
                featureBuffer.Shape = geometry;
                insertCursor.InsertFeature(featureBuffer);
            }

            // Flush the buffer to the geodatabase.
            insertCursor.Flush();
        }
    }
    catch (Exception)
    {
        // Handle the failure in a way appropriate to the application.
    }
    finally
    {
        // Disable load-only mode on the feature class.
        featureClassLoad.LoadOnlyMode = false;

        // Demote the exclusive schema lock to a shared lock.
        schemaLock.ChangeSchemaLock(esriSchemaLock.esriSharedSchemaLock);
    }
}

2 资源的释放

在进行数据查询时,如果不对游标进行释放,查询效率会越来越慢

2.1 使用Marsh.ReleaseComObject进行查询游标的释放

        private void find_allroads_lxbm(IFeature feature_ld, string lxbm)
        {
            try
            {
                ISpatialFilter spatialfilter = new SpatialFilterClass();
                spatialfilter.WhereClause = "[LXBM] = " + lxbm;
                spatialfilter.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
                //feature_ld该路段 feature_last下一路段
                while (feature_ld != null)
                {
                    IPolyline polyline = feature_ld.Shape as IPolyline;
                    //缓冲
                    IPoint end_pt = polyline.ToPoint;
                    ITopologicalOperator topo2 = end_pt as ITopologicalOperator;
                    IGeometry geo_buffer_end = topo2.Buffer(1);
                    spatialfilter.Geometry = geo_buffer_end;
                    IFeatureCursor featcursor_lastld = m_featcls.Search(spatialfilter, true);
                    IFeature feature_last = featcursor_lastld.NextFeature();
                    try
                    {
                        while (feature_last != null)
                        {                         
                           /****省略***/                                                 
                        }
                    }
                    catch (Exception exx)
                    {                        
                        MessageBox.Show(exx.ToString());
                    }                 
                    //下一个
                    feature_ld = feature_last;
             System.Runtime.InteropServices.Marshal.ReleaseComObject(featcursor_lastld);
                   
                }
                System.Runtime.InteropServices.Marshal.ReleaseComObject(spatialfilter);
                ////垃圾回收
                System.GC.Collect();
                System.GC.WaitForPendingFinalizers();
            }
            catch (Exception exx)
            {
                MessageBox.Show(exx.ToString());
            }           
        }  

2.2 分析

情况一:在使用完游标之后,将其赋值为null;这时候对象实际上是没有被释放掉的,因此 可能会报 '超出打开游标最大数’;

情况二:在使用完游标时,在循环末尾,用Marshal.ReleaseComObject方法来释放com对象,同时在使用Marshal.ReleaseComObject 方法时,并没有增加多余的时间,循环执行时间上还是跟不释放对象一样(小数据量比较)。

结论:

在使用ArcEngine中的游标对象时,一定要在使用完之后进行对象的释放,否则会不定时出现上面的错误;而且需要使用marshal.releasecomobject方法来进行对象的释放,赋值为null是达不到目的

Search()版本

IFeatureCursor pFeatureCursor = featureClass.Search(null, true);
IFeature pFeature = pFeatureCursor.NextFeature();
while(pFeature != null)
{
    //自已的一通操作
    pFeature.Store();//保存
    pFeature = pFeatureCursor.NextFeature();
}
Marshal.ReleaseComObject(pFeatureCursor);
pFeatureCursor = null;
GC.Collect();

Update()版本

IFeatureCursor pFeatureCursor = featureClass.Update(null, true);
IFeature pFeature = pFeatureCursor.NextFeature();
while(pFeature != null)
{
    //自已的一通操作
    pFeature.UpdateFeature(pFeature );//保存
    pFeature = pFeatureCursor.NextFeature();
}
Marshal.ReleaseComObject(pFeatureCursor);
pFeatureCursor = null;
GC.Collect();

Insert()版本

IFeatureCursor pFeatureCursor = featureClass.Insert(true);
IFeature pFeature = pFeatureCursor.NextFeature();
IFeatureBuffer pFeatureBuffer = featureClass.CreatFeatureBuffer();
while(pFeature != null)
{
    //对pFeatureBuffer执行一波操作
    pFeatureCursor.UpdateFeature(pFeatureBuffer);//保存
    pFeature = pFeatureCursor.NextFeature();
}
pFeatureCursor.Flush();
Marshal.ReleaseComObject(pFeatureCursor);
pFeatureCursor = null;
GC.Collect();

建议就是尽量避免对数据执行大量的循环查询操作,如果能采用内存优化的,就直接使用内存,这样可以快速提升程序的运行效率;但是对于,海量数据,迫不得已必须进行大量的循环操作时,请注意及时释放游标,释放内存资源,以确保程序性能。

3 Geadatebse API使用最佳实践

本主题讨论如何使用Geodatabase应用程序编程接口(API)中的某些组件,以优化性能、防止数据损坏和避免意外行为。伴随着对每个最佳实践的描述的是显示不正确或正确模式的代码示例。

本主题旨在成为使用Geodatabase API的开发人员的“备忘单”。有许多可以提高性能的最佳实践,也有许多可能损害性能或导致意外结果的常见错误。本主题中的信息是两者的组合,并基于支持事件、论坛帖子和其他第三方代码中的代码示例。

本主题中的代码示例用于说明周围段落中的文本,而不仅仅是提供可复制并粘贴到应用程序中的代码。在某些情况下,代码用于说明要避免的编程模式。在复制和粘贴本主题中的任何代码之前,请从代码的周围段落文本中确保它是要使用的实践示例,而不是要避免的示例。

3.1 对recycling的理解

回收是游标的一个属性,决定如何创建游标中的行。可以启用或禁用回收,并通过API在多个游标实例化方法(包括ITable.Search和ISelectionSet.Search)上显示为布尔参数。如果启用了回收,则无论从游标返回多少行,游标只为一行分配内存。这在内存使用和运行时间方面都提供了性能优势,但对于某些工作流来说有缺点。在任何时候只引用一行的情况下,回收都很有用,例如,绘制几何图形或将当前行的ObjectID显示到控制台窗口。当需要以某种方式比较光标中的多行时,或者当正在编辑行时,请避免循环使用。

Recycling is a property of cursors that determines how rows from the cursor are created. Recycling can be 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 only allocates 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. Recycling is useful 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 ObjectID to a console window. When multiple rows from a cursor need to be compared in some way, or when rows are being edited, avoid recycling. Consider the following code example that compares the first two geometries from a feature cursor to see if they are equal:

考虑下面的代码示例,该示例比较要素光标的前两个几何图形,以查看它们是否相等:[要避免使用的示例]

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);
    }
}

如果启用回收,则前面的代码始终返回true,因为 feature1和feature2引用将指向同一对象,第二个NextFeature调用不创建行,它将覆盖现有行的值。调用IRelationalOperator.Equals是将几何体与自身进行比较。出于同样的原因,“两个”特性的ObjectID或属性值之间的任何比较也表示相等。禁用回收是一种更为谨慎的方法,因为不恰当地使用非回收游标不太可能返回与上一个代码示例中的结果相同的意外结果,但它可能会对性能造成很大的影响

恰当案例

下面的代码示例将打开要素类上的搜索光标,并查找每个要素的面积之和。由于这不会引用任何先前获取的行,因此这是回收游标的理想候选:

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);
    }
}

前面的代码示例在geodatabase文件中的一个要素类上进行了测试,该文件包含大约500000个要素,结果如下(在下面,~表示大约):在启用回收的情况下,流程的工作集增加了约4%,而在禁用回收的情况下则增加了约48%。在禁用回收的情况下,该方法的运行时间大约是原来的2.25倍。其他类似的工作流在不恰当地使用非循环游标时可能会导致更大的差异,例如工作集增加了近250%,执行时间比启用循环时长12倍。【The preceding code example was tested on a feature class in a file geodatabase containing approximately 500,000 features with the following results (in the following, ~ indicates approximately): (1). The process's working set increased by ~4 percent with recycling enabled as opposed to ~48 percent with recycling disabled. (2). 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 percent and an execution time 12 times longer than with recycling enabled】

参数Recycling为True的时候理解为传引用,为False的时候理解为传值。因此在应用的时候应该注意的“传值”和“传址”的差异。
 我们知道引用传递(“传址”)效率较高,因此在绘画要素的时候可以采用True参数。但当要进行遍历后将Feature的Geometry加入某个集合或插入到其他FeatureClass的时候,必须使用传值调用,即参数为False,否则我们加入的都是最后一个变量中的Geometry(因为传地址嘛)。

3.2 存储FindField的结果

方法(如IClass.FindField和IFields.FindField)用于根据字段的名称检索字段在数据集或字段集合中的位置。依赖FindField而不是硬编码字段位置是一个好的做法,但是过度使用FindField可能会影响性能。【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】

考虑下面的代码示例,其中“NAME”属性是从游标的功能中检索的:【不当使用】

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")));
        }
    }
}

尽管FindField调用不是一个昂贵的操作,但是当涉及大量要素时,成本会增加。更改代码以重用FindField结果通常会将性能提高3-10%(在某些情况下甚至更高),而且不需要太多努力。

下列示例显示了一个较好的示例(检查FindField的值不为-1);如果返回为-1,说明没有找到;如果-1的值被用作value属性的参数(C#中的get_value和set_value方法),则不会返回描述性错误消息,因为value无法知道客户端要访问哪个字段

The following code example shows an additional good practice (checking that FindField values are not –1). If a field cannot be found, FindField returns –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 is not returned, as Value has no way of knowing what field the client intended to access.

正确做法:【先索引再取值】

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));
        }
    }
}

说白了,就我们先得到字段的索引,如果索引为-1,说明字段不存在;如果不为-1,我们再利用索引获取对应字段的值

3.3 DDL命令不能在编辑时使用

数据定义语言(DDL)命令是修改数据库架构的数据库命令。示例包括创建表、向表中添加新字段或删除索引。触发DDL命令的方法(如IFeatureWorkspace.CreateTable或IClass.AddField)不应在编辑会话中调用,因为DDL命令将提交当前打开的任何事务,因此在发生错误时无法回滚任何不需要的编辑。此实践还扩展到geodatabase模式修改,从数据库的角度来看,这不是真正的DDL,例如修改域,因为这些类型的操作显式提交其更改。这方面的一个实际示例是自定义编辑应用程序,该应用程序根据用户的编辑向编码值域添加新值,然后在应用程序尝试提交编辑时意外失败。在这种情况下,方法是维护用户提供的值列表,然后在编辑会话停止后添加这些值。

Data definition language (DDL) commands are database commands that modify the schema of a database. Examples include creating 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 an edit session, because DDL commands will commit any transactions that are currently open, making it impossible to rollback any unwanted edits if an error occurs.

This practice also extends to geodatabase schema modification that is not 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 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.

3.4 Calling Store inside of Store-triggered events

Geodatabase API公开了几个事件,这些事件允许开发人员在对方法调用存储时应用自定义行为,例如IObjectClassEvents.OnCreate和IRelatedObjectClassEvents.RelatedObjectCreated。实现通过这些方法定义自定义行为的类扩展或事件处理程序的开发人员,以及其他类似的开发人员,应确保不会在触发事件的行上再次调用存储,即使自定义行为导致了行的修改。再次调用对象上的存储将从模型中触发事件模型,从而导致意外行为。在某些情况下,这会导致无限递归,导致应用程序挂起,而在其他情况下,错误将随可能难以解释的消息返回。

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 triggers the event model from within the model, leading to unexpected behavior. In some cases, this results in infinite recursion causing an application to hang, while in others, errors are returned with messages that might be difficult to interpret.

下面的代码示例显示了一个简单的“时间戳”示例,该示例旨在创建要素时保持当前用户的名称,但会根据数据源产生不同种类的错误:

The following code example shows a simple "timestamp" example that is intended to maintain the current user's name on features being created, but produces different varieties of errors depending on the data source:不要这么干

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!
}

3.5 获取要素

IFeatureClass接口公开了两个类似的方法GetFeature和GetFeatures,用于按对象ID检索功能。前者检索单个特征并获取一个整数参数,而后者创建一个游标,该游标返回整数数组参数中指定的要素(它还具有一个参数,该参数指定游标是否将被循环使用)。出于性能目的,每当使用已知ObjectID检索多个功能时,请始终使用GetFeatures方法

The IFeatureClass interface exposes two similar methods—GetFeature and GetFeatures—for retrieving features by their ObjectIDs. The former retrieves a single feature and takes an integer parameter, while the latter creates 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, anytime more than one feature is being retrieved using a known ObjectID, always use the GetFeatures method. Compare the following two code examples:IFeatureClass.GetFeatures uses a conformant array parameter that makes it unsafe for use in .NET; IGeoDatabaseBridge.GetFeatures provides the same functionality in an interop-safe manner.

比较以下两个代码示例:IFeatureClass.GetFeatures使用一致数组参数,这使得在.NET中使用该参数不安全;IGeoDatabaseBridge.GetFeatures以互操作安全的方式提供相同的功能。

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("NAME");
    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));
        }
    }
}

前面的代码示例在请求单个要素时,具有相同的性能级别,但是GetFeatures示例仅在两个要素(尤其是在远程数据库中)上优于GetFeature示例,并且随着请求更多要素,这两个函数之间的差异也会增大。对于100个特性,GetFeature示例通常需要10-12次运行,而对于1000个特性,它通常需要20次运行。

The preceding code examples have the same level of performance if a single feature is being requested, but the GetFeatures example outperforms 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 100 features, the GetFeature example typically requires as much as 10–12 times to run, while with 1,000 features, it often takes up to 20 times as long.

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字段,因此正在修改该字段对象,然后再次将其添加到集合中。这可以使用以下两种不同的方法来避免:

  1. 在添加每个字段后,将字段和fieldEdit变量重新分配给新创建的字段对象。
  2. 为要添加到集合中的每个字段使用一组单独的变量,即“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:

  1. Reassign the field and fieldEdit variables to a newly created field object after each field is added.
  2. 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中的空间查询界面,如下图:

基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化_第1张图片

我们发现,IQueryByLayer接口中的BuferDistance对应“应用搜索距离”,BufferUnits对应搜索距离的单位,由于ArcGIS Desktop和ArcEngine都是基于ArcObjects,所以Desktop中的空间查询也是基于IQueryByLayer接口。


有兴趣关注一下作者,一起努力

基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化_第2张图片

 

你可能感兴趣的:(基于C#的ArcEngine二次开发37:循环查询过程的内存管理与性能优化)