AE SDK 翻译之: Geodatabase API best practices

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SONGYINGXU/article/details/78931496

http://help.arcgis.com/en/sdk/10.0/arcobjects_net/conceptualhelp/index.html#/Geodatabase_API_best_practices/000100000047000000/

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

  1. 在这个话题包括:

关于地理数据库API最佳实践
了解回收
存储FindField结果
在编辑会话中执行DDL
在Store触发的事件中调用Store
GetFeature和GetFeatures
不小心重复使用变量
插入和关系类通知
修改模式对象


关于地理数据库API最佳实践
本主题旨在成为使用地理数据库API的开发人员的“备忘单”。 有许多可以提高性能的最佳实践,以及许多可能会损害性能或导致意想不到的结果的常见错误。 本主题中的信息是两者的组合,基于支持事件,论坛帖子和其他第三方代码中的代码示例。
本主题中的代码示例用于说明周围段落中的文本,而不仅仅是提供可复制和粘贴到应用程序中的代码。 在某些情况下,代码被用来说明一个编程模式,以避免。 在复制和粘贴本主题中的任何代码之前,请确保从代码的周围段落文本中,这是一个使用练习的例子,而不是一个要避免的事例。

了解回收
回收是游标的一个属性,决定了如何创建游标中的行。 可以启用或禁用回收功能,并通过API作为布尔参数暴露给几个游标实例化方法,包括ITable.Search和ISelectionSet.Search。
如果启用了回收功能,游标只会为单个行分配内存,而不管游标返回的行数是多少。 这提供了内存使用和运行时间方面的性能优势,但是对于某些工作流程有缺陷。
在任何时候只有一行将被引用的情况下,例如,绘制几何图形或将当前行的ObjectID显示到控制台窗口时,回收很有用。 当需要以某种方式比较游标中的多行时,或者正在编辑行时,请避免回收。 考虑下面的代码示例,比较特征游标的前两个几何,看它们是否相等:

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

上面的代码示例已在文件地理数据库中的要素类上进行了测试,其中包含大约500,000个要素,其中包含以下结果(在下文中,〜表示大致):
这个过程的工作环境增加了约4%,而废弃物的回收率则提高了约48%。
随着残疾人的回收,这个方法花了大约2.25倍的时间运行。
其他类似的工作流程在不恰当地使用非循环游标时可能会导致更大的差异,例如工作集增加近250%,执行时间比启用循环的时间长12倍。

存储FindField结果
方法,如IClass.FindField和IFields.FindField用于根据名称检索数据集或字段集合中字段的位置。 依靠FindField而不是硬编码的字段位置是一个好习惯,但过度使用FindField可能会影响性能。 考虑下面的代码示例,其中从游标的特征中检索“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)。 如果找不到字段,则FindField返回-1。 如果-1值被用作Value属性的参数(C#中的get_Value和set_Value方法),则不会返回描述性错误消息,因为Value无法知道客户端要访问的字段。

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

在编辑会话中执行DDL
数据定义语言(DDL)命令是修改数据库模式的数据库命令。示例包括创建表格,向表格添加新字段或删除索引。触发DDL命令的方法(如IFeatureWorkspace.CreateTable或IClass.AddField)不应在编辑会话中调用,因为DDL命令将提交当前打开的任何事务,如果发生错误,则无法回滚任何不需要的编辑。
这种做法也延伸到从数据库的角度来看不是真正的DDL的地理数据库模式修改(例如修改域),因为这些类型的操作明确地提交了它们的改变。一个真实的例子是一个自定义编辑应用​​程序,它根据用户的编辑将新值添加到编码值域,然后在应用程序尝试提交编辑时意外失败。这种情况下的方法是维护用户提供的值列表,然后在编辑会话停止后添加它们。
在Store触发的事件中调用Store
地理数据库API公开了几个事件,这些事件允许开发人员在方法上调用Store时应用自定义行为,例如IObjectClassEvents.OnCreate和IRelatedObjectClassEvents.RelatedObjectCreated。开发人员实现通过这些方法定义自定义行为的类扩展或事件处理程序,以及像这样的其他方法,应确保在触发事件的行上不再调用Store,即使自定义行为导致行被修改。在对象上再次调用存储会从模型中触发事件模型,从而导致意外的行为。在某些情况下,这会导致无限递归,从而导致应用程序挂起,而在其他情况下,将返回错误信息,这些信息可能难以解释。
下面的代码示例显示了一个简单的“时间戳”示例,用于维护要创建的要素上当前用户的名称,但会根据数据源生成不同的错误类型:

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

GetFeature和GetFeatures
IFeatureClass接口公开了两个类似的方法 - GetFeature和GetFeatures - 通过它们的ObjectID检索要素。 前者检索单个特征并获取一个整型参数,而后者则创建一个游标,返回在整型数组参数中指定的特征(它也有一个参数,指定游标是否被回收)。
出于性能考虑,任何时候使用已知ObjectID检索多个特征时,总是使用GetFeatures方法。 比较以下两个代码示例:
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倍的时间。

不小心重复使用变量
使用Geodatabase API时,不小心重复使用变量会导致两种类型的复杂情况。 创建集合(如字段集合)时,最常见的是第一种类型的复杂化。 请参阅以下代码示例,该示例旨在创建一组包含ObjectID字段和字符串字段的字段:

private static IFields FieldSetCreation()
{
    // Create a field collection and a field.
    IFields fields = new FieldsClass();
    IFieldsEdit fieldsEdit = (IFieldsEdit)fields;
    IField field = new FieldClass();
    IFieldEdit fieldEdit = (IFieldEdit)field;

    // Add an ObjectID field.
    fieldEdit.Name_2 = "OBJECTID";
    fieldEdit.Type_2 = esriFieldType.esriFieldTypeOID;
    fieldsEdit.AddField(field);

    // Add a text field.
    fieldEdit.Name_2 = "NAME";
    fieldEdit.Type_2 = esriFieldType.esriFieldTypeString;
    fieldsEdit.AddField(field);

    return fields;
}

此代码不能如预期的那样工作的原因可能不会立即显现,并且在生成的字段集用于创建表时返回的错误消息可能没有多大帮助(这将对现有的重复字段桌子)。实际发生的是最终的字段集包含两个字段(两个相同的字符串字段)。由于“field”和“fieldEdit”变量仍然引用已添加的ObjectID字段,该字段对象正在被修改,然后再次添加到集合中。这可以避免使用以下两种不同的方法:
添加每个字段后,将字段和fieldEdit变量重新分配给新创建的字段对象。
为将要添加到集合的每个字段使用一组单独的变量,即“oidField”和“oidFieldEdit”
不小心重复使用变量导致的第二种复杂情况就是失去了所有应该使用ComReleaser类或Marshal.ReleaseComObject方法显式释放的对象的引用。考虑下面的代码示例:

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类时也会发生同样的问题。 生命周期管理是针对对象的,而不是针对变量的。 例如,在下面的代码示例中,只有第一个光标被正确管理:

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

插入和关系类通知
通知(也称为消息传递)是关系类的属性,它定义了在参与关系类的两个对象类之间发送哪个方向的消息。以下是四种通知类型:
无 - 简单的关系的典型
正向 - 典型的复合关系
落后
两个(双向)
这些消息确保组合关系,功能链接注释类和许多自定义类扩展的正确行为。但是,这种行为确实是有代价的。对触发通知的数据集进行编辑和插入明显比对不触发任何通知的数据集上的同样操作要慢。
对于插入,可以通过确保所有通知的类在任何插入发生之前打开来减轻此性能影响。以下代码示例基于模式,其中Parcel要素类参与了拥有Owners表的复合关系类,并且正在对要素类进行插入:

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

在这种情况下确保通知班级的表现好处是非常重要的。 在上述情况下,如果插入了1,000个要素,则无法打开通知的类通常会导致应用程序运行10-15次,只要通知的类打开即可。 当一个类触发多个类的通知时,这个特别重要,因为这个因子乘以被通知的类的数量(即如果通知五个未打开的类,则运行时间增加50-75倍)。

修改模式对象
每种类型的地理数据库对象数据集,域,字段等都在API中具有相应的类。开发人员应该意识到这些类可以分为以下两类:
那些在地理数据库(即表)中自动保留模式更改的表
那些没有的,就是领域,领域,指标
一个经典的例子是IClass.AddField和IFieldsEdit.AddField。当调用前者时,API会向数据库表中添加一个字段。当后者被调用时,一个字段被添加到内存中的字段集合中,但是不会改变实际的表格。许多开发人员已经发现了打开一个表,获取一个字段集合,并添加一个新的领域是不正确的工作流程的艰难的方式。
其他无效的工作流程包括以下内容:
使用IFieldEdit界面修改已经在地理数据库中创建的字段
使用IIndexesEdit界面修改已经在地理数据库中创建的索引集合
使用IIndexEdit界面修改已经在地理数据库中创建的索引
另一个类似的工作流程是从工作空间中检索一个域并对其进行修改,例如,将新的代码添加到编码的值域中。虽然这些更改不会自动保留在地理数据库中,但可以调用IWorkspaceDomains2.AlterDomain以使用修改后的对象覆盖持久化域。


要使用本主题中的代码,请在Visual Studio项目中引用以下程序集。 在代码文件中,您需要使用(C#)或Imports(VB .NET)指令来使用相应的名称空间(如果与程序集名称不同,则在下面的括号中给出):
ESRI.ArcGIS.ADF.Connection.Local
ESRI.ArcGIS.Geodatabase
ESRI.ArcGIS.System(ESRI.ArcGIS.esriSystem)

猜你喜欢

转载自blog.csdn.net/SONGYINGXU/article/details/78931496
今日推荐