Select to view content in your preferred language

EditOperation fails when creating and removing associations

1230
3
Jump to solution
09-28-2023 11:23 AM
rpepato_bizpoke
Occasional Contributor
Hello community,


I'm working on a migration of an existing ArcGIS Pro AddIn from 2.X to 3.X and, after the migration is complete, I started to get errors when using EditOperation instances to create and remove Utility Network associations.

As the AddIn is highly customized, I wrote a sample code containing the simplest AddIn that I could imagine to isolate the issue and illustrate the problem. All of the the AddIn relevant source code is (for the sake of simplicity and convenience) located at Button1.cs class. A copy of the sample code, including its Visual Studio project and solution is attached to this message for convenience.

Context:
I want to use the ArcGIS Pro SDK to remove and later recreate Junction-Junction associations for existing elements of the network.

Namely, the goal is to first disconnect a Patch Panel Port from a Fiber Connector from the Communications Utility Network Foundation 1.4. The elements involved in the operation are:
    1. Element's data source: Communications Junction Object
        Element's GlobalID: {F150968C-C032-487C-B5ED-62118ACCFE4D}
        Element's ObjectID: 10973
        Element's Asset Group: Port
        Element's Asset Type: Patch Panel Port

    2. Element's data source: Communications Junction Object
        Element's GlobalID: {0E4091D9-83B5-4112-92DF-B939C11B10C3}
        Element's ObjectID: 10607
        Element's Asset Group: Connector
        Element's Asset Type: Fiber

 

The target database for this error to happen is the file geodatabase part of the Communications Utility Network  Foundation 1.4 (although tests were made with previous versions [1.3] of the Communications Utility Network - and also on an enterprise 11.1 environment -  and the error was the same).
 
Runtime Environment:
    * ArcGIS Pro 3.1.3
    * Communications Utility Network Foundation 1.4 (also tested with version 1.3 and the same results applies)

 

Approach:
The sample source code uses samples from Esri official documentation whenever possible (referenced in code comments) and minor additions for required tasks like fetching rows from the database.

 

Problem:

The removal/creation of association between the elements described in the Context section consistently fails when running it from an EditOperation instance.

The EditOperation always returns false for the Execute() call and it looks like the API doens't provide any property with details about why did the operation fail.

As a result, disconnections and reconnections can't be performed using the SDK at 3.X (the same migrated code, before migration, works in version 2.9).

In fact the removal/creation of association also fails from the ArcGIS Pro interface (using Modify Associations dialog, for example). If you keep repeating the operation, sometimes it works but mostly fails consistently (we can see an error message in the screenshot with the failure - an although the message mentions an error, at the same time it says that the operation has succeeded, which is not the case).

 rpepato_bizpoke_0-1695988182472.png

 

This looks like a potential bug to me, but I'd like to discuss and validate with community first. Does anyone see any major problem in the source code below? Any hint about possible and explainable causes of the current behavior?

Sample source code:

 

 

 

using ArcGIS.Core.Data;
using ArcGIS.Core.Data.UtilityNetwork;
using ArcGIS.Desktop.Editing;
using ArcGIS.Desktop.Framework.Contracts;
using ArcGIS.Desktop.Framework.Dialogs;
using ArcGIS.Desktop.Framework.Threading.Tasks;
using ArcGIS.Desktop.Mapping;
using System.Collections.Generic;
using System.Linq;

namespace EditOperationDebug
{
    internal class Button1 : Button
    {
        /// <summary>
        /// Gets a reference to the Utility Network used in the current map
        /// </summary>
        /// <param name="layer">A layer reference that is part of the utility network</param>
        /// <returns></returns>
        /// 
        ///
        /// Copied from Esri code snippets at:
        ///     https://github.com/esri/arcgis-pro-sdk/wiki/ProSnippets-UtilityNetwork#get-a-utility-network-from-a-layer
        private UtilityNetwork GetUtilityNetworkFromLayer(Layer layer)
        {
            UtilityNetwork utilityNetwork = null;

            if (layer is UtilityNetworkLayer)
            {
                UtilityNetworkLayer utilityNetworkLayer = layer as UtilityNetworkLayer;
                utilityNetwork = utilityNetworkLayer.GetUtilityNetwork();
            }

            else if (layer is SubtypeGroupLayer)
            {
                CompositeLayer compositeLayer = layer as CompositeLayer;
                utilityNetwork = GetUtilityNetworkFromLayer(compositeLayer.Layers.First());
            }

            else if (layer is FeatureLayer)
            {
                FeatureLayer featureLayer = layer as FeatureLayer;
                using (FeatureClass featureClass = featureLayer.GetFeatureClass())
                {
                    if (featureClass.IsControllerDatasetSupported())
                    {
                        IReadOnlyList<Dataset> controllerDatasets = new List<Dataset>();
                        controllerDatasets = featureClass.GetControllerDatasets();
                        foreach (Dataset controllerDataset in controllerDatasets)
                        {
                            if (controllerDataset is UtilityNetwork)
                            {
                                utilityNetwork = controllerDataset as UtilityNetwork;
                            }
                            else
                            {
                                controllerDataset.Dispose();
                            }
                        }
                    }
                }
            }
            return utilityNetwork;
        }

        /// <summary>
        /// Queries and returns an UN Element instance from a given GlobalID
        /// </summary>
        /// <param name="un">Reference to the utility network</param>
        /// <param name="tableName">The name of the table (in ToC) that contains the row that will be matched by GlobalID</param>
        /// <param name="globalID">The GlobalID of the row that we want to get an Element instance from</param>
        /// <returns>The UN Element corresponding to the combination of UN, tableName and globalID parameters if a row is found, null otherwise</returns>
        private Element GetElementFromGlobalID(UtilityNetwork un, string tableName, string globalID)
        {
            Table t = null;

            IReadOnlyList<StandaloneTable> tables = MapView.Active.Map.GetStandaloneTablesAsFlattenedList();
            foreach (var table in tables)
            {
                if (table.Name.Equals(tableName))
                {
                    t = table.GetTable();
                }
            }
            
            string filterClause = "GlobalID = '{" + globalID + "}'";
            QueryFilter qf = new QueryFilter()
            {
                WhereClause = filterClause,
            };

            using (RowCursor cursor = t.Search(qf))
            {
                if (cursor.MoveNext())
                {
                    Row r = cursor.Current;
                    return un.CreateElement(r);
                }
            }
            return null;
        }
        /// <summary>
        /// Gets an instance of a Terminal by its name
        /// </summary>
        /// <param name="un">Reference to the Utility Network</param>
        /// <param name="terminalName">Name of the terminal that used for filter</param>
        /// <returns>A Terminal instance if the filter fully matches a terminal name, null otherwise.</returns>
        private Terminal GetTerminal(UtilityNetwork un, string terminalName)
        {
            var terminalConfigurations = un.GetDefinition().GetTerminalConfigurations();
            foreach (var terminalConfiguration in terminalConfigurations)
            {
                foreach(var terminal in terminalConfiguration.Terminals)
                {
                    if (terminal.Name.Equals(terminalName))
                    {
                        return terminal;
                    }
                }
            }
            return null;
        }


        /// <summary>
        /// Removes a JunctionJunctionConnectivity association for Elements 1 and 2 
        /// </summary>
        /// <param name="r1">The RowHandle for the first element involved in the association</param>
        /// <param name="r2">The RowHandle for the second element involved in the association</param>
        /// <returns>A boolean value containing the result of the operation</returns>
        private bool DisconnectAssociation(RowHandle r1, RowHandle r2)
        {
            EditOperation editOp = new EditOperation();
            editOp.Name = "Disonnect association";
            AssociationDescription associationDescription = new AssociationDescription(AssociationType.JunctionJunctionConnectivity, r1, r2);
            editOp.Delete(associationDescription);
            return editOp.Execute();
        }

        private bool ConnectAssociation(RowHandle r1, RowHandle r2)
        {
            EditOperation editOp = new EditOperation();
            editOp.Name = "Connect association";
            AssociationDescription associationDescription = new AssociationDescription(AssociationType.JunctionJunctionConnectivity, r1, r2);
            editOp.Create(associationDescription);
            return editOp.Execute();
        }

        protected override void OnClick()
        {
            QueuedTask.Run(() =>
            {
                // Gets a reference for the Communications Device layer in the current map (Communications Utility Network)
                Layer communicationsDeviceLayer = MapView.Active.Map.Layers.First(x => x.Name.Equals("Communications Device"));
                UtilityNetwork un = GetUtilityNetworkFromLayer(communicationsDeviceLayer);

                // Defines the table name that will be used in the connect and disconnect operations
                string tableName = "Communications Junction Object";

                // Communications Junction Object
                // Global ID: {F150968C-C032-487C-B5ED-62118ACCFE4D}
                // Object ID: 10973
                // Asset Group: Port
                // Asset Type: Patch Panel Port
                Element patchPanelPort = GetElementFromGlobalID(un, tableName, "F150968C-C032-487C-B5ED-62118ACCFE4D");


                // Communications Junction Object
                // Global ID: {0E4091D9-83B5-4112-92DF-B939C11B10C3}
                // Object ID: 10607
                // Asset Group: Connector
                // Asset Type: Fiber
                Element fiberConnector = GetElementFromGlobalID(un, tableName, "0E4091D9-83B5-4112-92DF-B939C11B10C3");

                // Try to disconnect the patch panel port from the fiber connector
                // Also tested with elements in reverse order during the call as
                //      DisconnectAssociation(new RowHandle(fiberConnector, un), new RowHandle(patchPanelPort, un));
                var disconnectResult = DisconnectAssociation(new RowHandle(patchPanelPort, un), new RowHandle(fiberConnector, un));

                if (!disconnectResult)
                {
                    MessageBox.Show("Can't disconnect elements. Something unexpected did happen");
                    return;
                }

                // Gets a reference to the C:Front terminal
                // This is required because orignally, the involved elements were connected at
                // C:Front terminal in both elements
                var cFrontTerminal = GetTerminal(un, "C:Front");
                patchPanelPort.Terminal = cFrontTerminal;
                fiberConnector.Terminal = cFrontTerminal;

                // Try to connect the patch panel port from the fiber connector
                var connectResult = ConnectAssociation(new RowHandle(fiberConnector, un), new RowHandle(patchPanelPort, un));
                if (!connectResult)
                {
                    MessageBox.Show("Can't connect elements. Something unexpected did happen");
                }
;            });
        }
    }
}

 

 

Steps to Reproduce:
    1. Install the AddIn attached to this thread (or download and compile the source code for installation if you prefer)
    2. Open up ArcGIS Pro 3.1.3
    3. Open up the default Communications Utility Network Foundation v1.4 project
    4. Open up the Communications Utility Network map
    4. On the AddIn tab on the ribbon, click at Button1 and check the results in the message box that will popup.
 
Development/Build Environment:
    * Visual Studio 2022
    * ArcGIS Pro SDK for .NET 3.1.0.41833
    * Microsoft .NET SDK 7.0.100 (x64) 7.1.22.52112  
 
Extra Information:
An extract of the network rules (Junction-Junction only, as this is the target of our test) is attached for reference. A link to the original database used in the tests can be found here.

Thank you,
 
Roberto.
 





1 Solution

Accepted Solutions
BillBott
Frequent Contributor

BUG-000161632: EditOperaion.Create() or Delete() throws "Edit Operation Failed" Error when creating/deleting Utility Network Associations in ArcGIS Pro SDK 3.1.

Solution is to use UtilityNetwork class instead, something like this:

 

 

HashSet<Table> dirtyTables = new HashSet<Table>();
Table tableFrom = this.utilityNetwork.GetTable(fromElement.NetworkSource);
Table tableTo = this.utilityNetwork.GetTable(toElement.NetworkSource);
dirtyTables.Add(tableFrom);
dirtyTables.Add(tableTo);

editOperation.Callback(
context =>
{
var newAssociation = new Association(this.AssociationType, fromElement, toElement);
this.utilityNetwork.AddAssociation(newAssociation);
}, dirtyTables);

bool result = await editOperation.ExecuteAsync();
 

 

 

 

View solution in original post

3 Replies
BillBott
Frequent Contributor

BUG-000161632: EditOperaion.Create() or Delete() throws "Edit Operation Failed" Error when creating/deleting Utility Network Associations in ArcGIS Pro SDK 3.1.

Solution is to use UtilityNetwork class instead, something like this:

 

 

HashSet<Table> dirtyTables = new HashSet<Table>();
Table tableFrom = this.utilityNetwork.GetTable(fromElement.NetworkSource);
Table tableTo = this.utilityNetwork.GetTable(toElement.NetworkSource);
dirtyTables.Add(tableFrom);
dirtyTables.Add(tableTo);

editOperation.Callback(
context =>
{
var newAssociation = new Association(this.AssociationType, fromElement, toElement);
this.utilityNetwork.AddAssociation(newAssociation);
}, dirtyTables);

bool result = await editOperation.ExecuteAsync();
 

 

 

 
krantiYadav75
Emerging Contributor

This solution worked. Thanks BillBott.

krantiYadav75
Emerging Contributor

Hi Roberto,

Did you tried to create association between Fiber to Fiber having splice in between? If have solution then can you please share.

Regards,

Kranti 

 

0 Kudos