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.
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).
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?
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");
}
; });
}
}
}
Solved! Go to Solution.
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();
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();
This solution worked. Thanks BillBott.
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