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

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

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.


相关文章
|
7月前
|
SQL 数据库 C#
C# .NET面试系列十一:数据库SQL查询(附建表语句)
#### 第1题 用一条 SQL 语句 查询出每门课都大于80 分的学生姓名 建表语句: ```sql create table tableA ( name varchar(10), kecheng varchar(10), fenshu int(11) ) DEFAULT CHARSET = 'utf8'; ``` 插入数据 ```sql insert into tableA values ('张三', '语文', 81); insert into tableA values ('张三', '数学', 75); insert into tableA values ('李四',
168 2
C# .NET面试系列十一:数据库SQL查询(附建表语句)
|
1月前
|
安全 编译器 程序员
C# 中 foreach 循环和 for 循环深度比较
为什么建议你多数情况下使用 foreach 进行遍历循环?看完你就明白了
|
29天前
|
开发框架 .NET API
以C#一分钟浅谈:GraphQL 数据类型与查询
本文从C#开发者的角度介绍了GraphQL的基本概念、核心组件及其实现方法。GraphQL由Facebook开发,允许客户端精确请求所需数据,提高应用性能。文章详细讲解了如何在C#中使用`GraphQL.NET`库创建Schema、配置ASP.NET Core,并讨论了GraphQL的数据类型及常见问题与解决方案。通过本文,C#开发者可以更好地理解并应用GraphQL,构建高效、灵活的API。
99 64
|
25天前
|
开发框架 .NET 测试技术
C# 一分钟浅谈:GraphQL 数据类型与查询
本文介绍了GraphQL的基本概念、数据类型及查询方法,重点从C#角度探讨了GraphQL的应用。通过Hot Chocolate库的实例,展示了如何在ASP.NET Core中实现GraphQL API,包括安装、定义Schema、配置及运行项目。文中还讨论了常见问题与解决方案,旨在帮助开发者更好地理解和使用GraphQL。
26 2
|
6月前
|
存储 Java C#
C# 中的值类型与引用类型:内存大小解析
C# 中的值类型与引用类型:内存大小解析
|
2月前
|
Java C#
如何避免在C#循环中使用await
如何避免在C#循环中使用await
131 9
|
2月前
|
SQL 缓存 分布式计算
C#如何处理上亿级数据的查询效率
C#如何处理上亿级数据的查询效率
47 1
|
3月前
|
开发框架 自然语言处理 .NET
C#一分钟浅谈:LINQ 查询表达式的使用技巧
【9月更文挑战第6天】LINQ(Language Integrated Query)是C#开发中的强大工具,使查询数据集合变得简单且接近自然语言。本文从基础入手,通过具体示例讲解LINQ查询表达式的使用技巧,包括过滤、排序和分组等操作。同时,文章还探讨了常见问题及解决方法,如性能优化、过早枚举和类型转换等,帮助开发者写出更高效、易维护的代码。
105 15
|
2月前
|
C# 开发工具 Windows
C# 获取Windows系统信息以及CPU、内存和磁盘使用情况
C# 获取Windows系统信息以及CPU、内存和磁盘使用情况
72 0
|
7月前
|
缓存 Java
Java中循环创建String对象的内存管理分析
Java中循环创建String对象的内存管理分析
76 2

热门文章

最新文章