天天看點

基于C#的ArcEngine二次開發37:循環查詢過程的記憶體管理與性能優化1 查詢接口函數解析2 資源的釋放3 Geadatebse API使用最佳實踐4 空間查詢優化

目錄

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
///<summary>Finds the first feature in a GeoFeature layer by supplying an point.  The point could come from a mouse click in the map.</summary>
///
///<param name="searchTolerance">A System.Double that is the number of map units to search. Example: 25</param>
///<param name="point">An IPoint interface in map units where the user clicked on the map</param>
///<param name="geoFeatureLayer">An ILayer interface to search upon</param>
///<param name="activeView">An IActiveView interface</param>
/// 
///<returns>An IFeature interface that is the first feature found in the GeoFeatureLayer.</returns>
/// 
///<remarks></remarks>
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 查詢接口函數解析2 資源的釋放3 Geadatebse API使用最佳實踐4 空間查詢優化

我們發現,IQueryByLayer接口中的BuferDistance對應“應用搜尋距離”,BufferUnits對應搜尋距離的機關,由于ArcGIS Desktop和ArcEngine都是基于ArcObjects,是以Desktop中的空間查詢也是基于IQueryByLayer接口。

有興趣關注一下作者,一起努力

基于C#的ArcEngine二次開發37:循環查詢過程的記憶體管理與性能優化1 查詢接口函數解析2 資源的釋放3 Geadatebse API使用最佳實踐4 空間查詢優化

繼續閱讀