ProSnippets KnowledgeGraph - Esri/arcgis-pro-sdk GitHub Wiki

Language:              C#  
Subject:               KnowledgeGraph  
Contributor:           ArcGIS Pro SDK Team <[email protected]>  
Organization:          esri, http://www.esri.com  
Date:                  10/22/2024  
ArcGIS Pro:            3.4  
Visual Studio:         2022  
.NET Target Framework: .Net 8  

KnowledgeGraph Datastore

Opening a Connection to a KnowledgeGraph

string url =
  @"https://acme.server.com/server/rest/services/Hosted/AcmeKnowledgeGraph/KnowledgeGraphServer";

QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  try
  {
    //Open a connection
    using (var kg = new KnowledgeGraph(kg_props))
    {
      //TODO...use the KnowledgeGraph
    }
  }
  catch (GeodatabaseNotFoundOrOpenedException ex)
  {
    System.Diagnostics.Debug.WriteLine(ex.ToString());
  }
});

Getting a Connection from a KnowledgeGraphLayer

var kgLayer = MapView.Active.Map.GetLayersAsFlattenedList()
        .OfType<KnowledgeGraphLayer>().FirstOrDefault();


QueuedTask.Run(() =>
{
  // use the layer directly
  KnowledgeGraph datastore = kgLayer.GetDatastore();
  
  // or you can use any of the sub items since
  //KnowledgeGraphLayer is a composite layer - get the first 
  // child feature layer or standalone table
  var featlayer = kgLayer?.GetLayersAsFlattenedList()?
                  .OfType<FeatureLayer>()?.FirstOrDefault();
  KnowledgeGraph kg = null;
  if (featlayer != null)
  {
    using (var fc = featlayer.GetFeatureClass())
      kg = fc.GetDatastore() as KnowledgeGraph;
    //TODO use KnowledgeGraph
  }
  else
  {
    //try standalone table
    var stbl = kgLayer?.GetStandaloneTablesAsFlattenedList()?
                    .FirstOrDefault();
    if (stbl != null)
    {
      using (var tbl = stbl.GetTable())
        kg = tbl.GetDatastore() as KnowledgeGraph;
      //TODO use KnowledgeGraph
    }
  }
});

Retrieving GDB FeatureClasses and Definitions

QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  //Connect to the KnowledgeGraph datastore
  //KnowledgeGraph datastores contain tables and feature classes
  using (var kg = new KnowledgeGraph(kg_props))
  {
    //Get the featureclass definitions from the KG datastore
    var fc_defs = kg.GetDefinitions<FeatureClassDefinition>();
    //For each feature class definition, get the corresponding
    //feature class. Note: The name of the feature class will match 
    //the name of its corresponding KnowledgeGraph named object type
    //in the KnowledgeGraph graph data model
    foreach (var fc_def in fc_defs)
    {
      var fc_name = fc_def.GetName();
      using (var fc = kg.OpenDataset<FeatureClass>(fc_name))
      {
        //TODO - use the feature class
      }
    }
  }
});

Retrieving GDB Tables and Definitions

QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  //Connect to the KnowledgeGraph datastore
  //KnowledgeGraph datastores contain tables and feature classes
  using (var kg = new KnowledgeGraph(kg_props))
  {
    //Get the table definitions from the KG datastore
    var tbl_defs = kg.GetDefinitions<TableDefinition>();
    //For each table definition, get the corresponding
    //table. Note: The name of the table will match the name
    //of its corresponding KnowledgeGraph named object type in
    //the KnowledgeGraph graph data model
    foreach (var tbl_def in tbl_defs)
    {
      var tbl_name = tbl_def.GetName();
      using (var fc = kg.OpenDataset<Table>(tbl_name))
      {
        //TODO - use the table
      }
    }
  }
});

Get service Uri from KG datastore

QueuedTask.Run(() =>
{
  var connector = kg.GetConnector() as KnowledgeGraphConnectionProperties;
  var uri = connector.URL;
  var serviceUri = uri.AbsoluteUri;
});

Transform a set of objectIDs to IDs for an entity

QueuedTask.Run(() =>
{
  var oidList = new List<long>() { 260294, 678, 3523, 3, 669, 93754 };
  var idList = kg.TransformToIDs(entityName, oidList);

});

Transform a set of IDs to objectIDs for an entity

QueuedTask.Run(() =>
{
  var idList = new List<object>() { "{CA2EF786-A10E-4B40-9737-9BDDDEA127B0}",
                                    "{14B5AD65-890D-4062-90A7-C42C23B0066E}",
                                    "{A428D1F6-CB00-4559-AAFD-40885A4F2340}"};
  var oidList = kg.TransformToObjectIDs(entityName, idList);

});

KnowledgeGraph Graph Data Model

Retrieving the Data Model

QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  using (var kg = new KnowledgeGraph(kg_props))
  {
    //Get the KnowledgeGraph Data Model
    using (var kg_dm = kg.GetDataModel())
    {
      //TODO use KG data model...
    }
  }
});

Get Data Model Properties

QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  using (var kg = new KnowledgeGraph(kg_props))
  {
    //Get the KnowledgeGraph Data Model
    using (var kg_dm = kg.GetDataModel())
    {
      var kg_name = System.IO.Path.GetFileName(
               System.IO.Path.GetDirectoryName(kg_props.URL.ToString()));

      System.Diagnostics.Debug.WriteLine(
        $"\r\n'{kg_name}' Datamodel:\r\n-----------------");
      var time_stamp = kg_dm.GetTimestamp();
      var sr = kg_dm.GetSpatialReference();
      System.Diagnostics.Debug.WriteLine($"Timestamp: {time_stamp}");
      System.Diagnostics.Debug.WriteLine($"Sref: {sr.Wkid}");
      System.Diagnostics.Debug.WriteLine(
        $"IsStrict: {kg_dm.GetIsStrict()}");
      System.Diagnostics.Debug.WriteLine(
        $"OIDPropertyName: {kg_dm.GetOIDPropertyName()}");
      System.Diagnostics.Debug.WriteLine(
        $"IsArcGISManaged: {kg_dm.GetIsArcGISManaged()}");
    }
  }
});

Get Data Model Identifier Info

QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  using (var kg = new KnowledgeGraph(kg_props))
  {
    //Get the KnowledgeGraph Data Model
    using (var kg_dm = kg.GetDataModel())
    {
      var kg_id_info = kg_dm.GetIdentifierInfo();
      var kg_id_gen = kg_id_info.GetIdentifierGeneration();
      if (kg_id_info is KnowledgeGraphNativeIdentifier kg_ni)
      {
        System.Diagnostics.Debug.WriteLine(
          $"IdentifierInfo: KnowledgeGraphNativeIdentifier");
      }
      else if (kg_id_info is KnowledgeGraphUniformIdentifier kg_ui)
      {
        System.Diagnostics.Debug.WriteLine(
          $"IdentifierInfo: KnowledgeGraphUniformIdentifier");
        System.Diagnostics.Debug.WriteLine(
          $"IdentifierName: '{kg_ui.GetIdentifierName()}'");
      }
      System.Diagnostics.Debug.WriteLine(
        $"Identifier MethodHint: {kg_id_gen.GetMethodHint()}");
    }
  }
});

Get Data Model MetaEntityTypes/Provenance

//Provenance entity type is stored as a MetaEntityType 
QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  using (var kg = new KnowledgeGraph(kg_props))
  {
    //Get the KnowledgeGraph Data Model
    using (var kg_dm = kg.GetDataModel())
    {
      var dict_types = kg_dm.GetMetaEntityTypes();
      //If there is no provenance then MetaEntityTypes will be
      //an empty collection
      foreach (var kvp in dict_types)
      {
        var meta_entity_type = kvp.Value;
        if (meta_entity_type.GetRole() ==
            KnowledgeGraphNamedObjectTypeRole.Provenance)
        {
          //TODO - use the provenance entity type
          var name = meta_entity_type.GetName();

        }
      }

    }
  }
});

Get Whether KG Supports Provenance

internal string GetProvenanceEntityTypeName(KnowledgeGraphDataModel kg_dm)
{
  var entity_types = kg_dm.GetMetaEntityTypes();
  foreach (var entity_type in entity_types)
  {
    if (entity_type.Value.GetRole() == KnowledgeGraphNamedObjectTypeRole.Provenance)
      return entity_type.Value.GetName();
  }
  return "";
}
internal bool KnowledgeGraphSupportsProvenance(KnowledgeGraph kg)
{
  //if there is a provenance entity type then the KnowledgeGraph
  //supports provenance
  return !string.IsNullOrEmpty(
    GetProvenanceEntityTypeName(kg.GetDataModel()));


  // OR use the KnowledgeGraphPropertyInfo
  var propInfo = kg.GetPropertyNameInfo();
  return propInfo.SupportsProvenance;
}

Get Whether KG Supports Provenance using KnowledgeGraphPropertyInfo

internal void KnowledgeGraphProvenance(KnowledgeGraph kg)
{
  // use the KnowledgeGraphPropertyInfo
  var propInfo = kg.GetPropertyNameInfo();
  var supportsProvenance = propInfo.SupportsProvenance;
  var provenanceType = propInfo.ProvenanceTypeName;
  var provenanceInfo = propInfo.ProvenancePropertyInfo;
}

Get KnowledgeGraph Entity Types

QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  using (var kg = new KnowledgeGraph(kg_props))
  {
    //Get the KnowledgeGraph Data Model
    using (var kg_dm = kg.GetDataModel())
    {
      var dict_types = kg_dm.GetEntityTypes();

      foreach (var kvp in dict_types)
      {
        var entity_type = kvp.Value;
        var role = entity_type.GetRole();
        //note "name" will be the same name as the corresponding
        //feature class or table in the KG's relational gdb model
        var name = entity_type.GetName();
        var alias = entity_type.GetAliasName();
        var objectIDPropertyName = entity_type.GetObjectIDPropertyName();
        //etc

      }

    }
  }
});

Get Whether KG Has a Document Type

internal string GetDocumentEntityTypeName(KnowledgeGraphDataModel kg_dm)
{
  var entity_types = kg_dm.GetEntityTypes();
  foreach (var entity_type in entity_types)
  {
    var role = entity_type.Value.GetRole();
    if (role == KnowledgeGraphNamedObjectTypeRole.Document)
      return entity_type.Value.GetName();
  }
  return "";//Unusual - probably Neo4j user-managed
}

internal bool KnowledgeGraphHasDocumentType(KnowledgeGraph kg)
{
  //uncommon for there not to be a document type
  return !string.IsNullOrEmpty(
    GetDocumentEntityTypeName(kg.GetDataModel()));
}

Check Whether A KG Entity Is a Document

//Use GetDocumentEntityTypeName(KnowledgeGraphDataModel kg_dm) from
//the 'Get Whether KG Has a Document Type' snippet to
//get the documentNameType parameter
protected bool GetEntityIsDocument(KnowledgeGraphEntityValue entity,
  string documentNameType = "")
{
  if (string.IsNullOrEmpty(documentNameType))
    return false;
  return entity.GetTypeName() == documentNameType;
}

Check Whether A Graph Type has a Spatial Property

//Use GetDocumentEntityTypeName(KnowledgeGraphDataModel kg_dm) from
//the 'Get Whether KG Has a Document Type' snippet to
//get the documentNameType parameter
protected bool HasGeometry(KnowledgeGraphNamedObjectType kg_named_obj)
{
  var props = kg_named_obj.GetProperties();
  return props.Any(prop => prop.FieldType == FieldType.Geometry);
}

Get KnowledgeGraph Relationship Types

QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  using (var kg = new KnowledgeGraph(kg_props))
  {
    //Get the KnowledgeGraph Data Model
    using (var kg_dm = kg.GetDataModel())
    {
      var dict_types = kg_dm.GetRelationshipTypes();

      foreach (var kvp in dict_types)
      {
        var rel_type = kvp.Value;
        var role = rel_type.GetRole();
        //note "name" will be the same name as the corresponding
        //feature class or table in the KG's relational gdb model
        var name = rel_type.GetName();
        //etc.
        //Get relationship end points
        var end_points = rel_type.GetEndPoints();
        foreach (var end_point in end_points)
        {
          System.Diagnostics.Debug.WriteLine(
            $"Origin: '{end_point.GetOriginEntityTypeName()}', " +
            $"Destination: '{end_point.GetDestinationEntityTypeName()}'");
        }
      }

    }
  }
});

Get All KnowledgeGraph Graph Types

QueuedTask.Run(() =>
{
  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  using (var kg = new KnowledgeGraph(kg_props))
  {
    //Get the KnowledgeGraph Data Model
    using (var kg_datamodel = kg.GetDataModel())
    {
      var entities = kg_datamodel.GetEntityTypes();
      var relationships = kg_datamodel.GetRelationshipTypes();
      var provenance = kg_datamodel.GetMetaEntityTypes();

      var all_graph_types = new List<KnowledgeGraphNamedObjectType>();
      all_graph_types.AddRange(entities.Values);
      all_graph_types.AddRange(relationships.Values);
      all_graph_types.AddRange(provenance.Values);

      System.Diagnostics.Debug.WriteLine("\r\nGraph Types");

      int c = 0;
      foreach (var graph_type in all_graph_types)
      {
        var type_name = graph_type.GetName();
        var role = graph_type.GetRole().ToString();

        //equivalent to:
        //var fields = featClassDef.GetFields().Select(f => f.Name).ToList();
        //var field_names = string.Join(",", fields);
        var props = graph_type.GetProperties().Select(p => p.Name).ToList();
        var prop_names = string.Join(",", props);

        System.Diagnostics.Debug.WriteLine($"[{c++}]: " +
            $"{type_name}, {role}, {prop_names}");
      }
    }
  }
});

KnowledgeGraphLayer Creation with Maps

Create a KG Layer containing all Entity and Relate types

QueuedTask.Run(() =>
{
  //With a connection to a KG established or source uri available...
  //Create a KnowledgeGraphLayerCreationParams
  var kg_params = new KnowledgeGraphLayerCreationParams(kg)
  {
    Name = "KG_With_All_Types",
    IsVisible = false
  };
  //Or
  var kg_params2 = new KnowledgeGraphLayerCreationParams(new Uri(url))
  {
    Name = "KG_With_All_Types",
    IsVisible = false
  };
  //Call layer factory with map or group layer container. 
  //A KG layer containing a feature layer and/or standalone table per
  //entity and relate type (except provenance) is created
  var kg_layer = LayerFactory.Instance.CreateLayer<KnowledgeGraphLayer>(
      kg_params, map);

});

Create a KG Layer containing a subset of Entity and Relate types

QueuedTask.Run(() =>
{
  //To create a KG layer (on a map or link chart) with a subset of
  //entities and relates, you must create an "id set". The workflow
  //is very similar to how you would create a SelectionSet.

  //First, create a dictionary containing the names of the types to be
  //added plus a corresponding list of ids (just like with a selection set).
  //Note: null or an empty list means add all records for "that" type..
  var kg_datamodel = kg.GetDataModel();
  //Arbitrarily get the name of the first entity and relate type
  var first_entity = kg_datamodel.GetEntityTypes().Keys.First();
  var first_relate = kg_datamodel.GetRelationshipTypes().Keys.First();

  //Make entries in the dictionary for each
  var dict = new Dictionary<string, List<long>>();
  dict.Add(first_entity, new List<long>());//Empty list means all records
  dict.Add(first_relate, null);//null list means all records
  //or specific records - however the ids are obtained
  //dict.Add(entity_or_relate_name, new List<long>() { 1, 5, 18, 36, 78});

  //Create the id set...
  var idSet = KnowledgeGraphLayerIDSet.FromDictionary(kg, dict);

  //Create the layer params and assign the id set
  var kg_params = new KnowledgeGraphLayerCreationParams(kg)
  {
    Name = "KG_With_ID_Set",
    IsVisible = false,
    IDSet = idSet
  };

  //Call layer factory with map or group layer container
  //A KG layer containing just the feature layer(s) and/or standalone table(s)
  //for the entity and/or relate types + associated records will be created
  var kg_layer = LayerFactory.Instance.CreateLayer<KnowledgeGraphLayer>(
      kg_params, map);

});

Using LayerFactory.Instance.CanCreateLayer with KG Create Layer Params

QueuedTask.Run(() =>
{
  //Feature class and/or standalone tables representing KG entity and
  //relate types can only be added to a map (or link chart) as a child
  //of a KnowledgeGraph layer....

  //For example:
  var fc = kg.OpenDataset<FeatureClass>("Some_Entity_Or_Relate_Name");
  try
  {
    //Attempting to create a feature layer containing the returned fc
    //NOT ALLOWED - can only be a child of a KG layer
    var fl_params_w_kg = new FeatureLayerCreationParams(fc);
    //CanCreateLayer will return false
    if (!(LayerFactory.Instance.CanCreateLayer<FeatureLayer>(
      fl_params_w_kg, map)))
    {
      System.Diagnostics.Debug.WriteLine(
        $"Cannot create a feature layer for {fc.GetName()}");
      return;
    }
    //This will throw an exception
    LayerFactory.Instance.CreateLayer<FeatureLayer>(fl_params_w_kg, map);
  }
  catch (Exception ex)
  {
    System.Diagnostics.Debug.WriteLine(ex.ToString());
  }

  //Can only be added as a child of a parent KG
  var dict = new Dictionary<string, List<long>>();
  dict.Add(fc.GetName(), new List<long>());
  var kg_params = new KnowledgeGraphLayerCreationParams(kg)
  {
    Name = $"KG_With_Just_{fc.GetName()}",
    IsVisible = false,
    IDSet = KnowledgeGraphLayerIDSet.FromDictionary(kg, dict)
  };
  var kg_layer = LayerFactory.Instance.CreateLayer<KnowledgeGraphLayer>(
    kg_params, map);

});

KnowledgeGraph Layer

Get and Set a KG Layer IDSet

var kg_layer = MapView.Active.Map.GetLayersAsFlattenedList()
                .OfType<KnowledgeGraphLayer>().FirstOrDefault();
if (kg_layer == null)
  return;

QueuedTask.Run(() =>
{
  //Get the existing kg layer id set and convert to a dictionary
  var layer_id_set = kg_layer.GetIDSet();
  var dict = layer_id_set.ToOIDDictionary();//Empty list means all records

  //Create an id set from a dictionary
  var dict2 = new Dictionary<string, List<long>>();
  dict2.Add("Enity_Or_Relate_Type_Name1", null);//Null means all records
  dict2.Add("Enity_Or_Relate_Type_Name2", new List<long>());//Empty list means all records
  dict2.Add("Enity_Or_Relate_Type_Name3",
    new List<long>() { 3, 5, 9, 101, 34 });//Explicit list of ids
  //dict2.Add("Enity_Or_Relate_Type_Name4",
  // new List<long>() { etc, etc });

  //Create the id set
  var idset = KnowledgeGraphLayerIDSet.FromDictionary(kg, dict2);

  //Can be used to create a layer, link chart, append to link chart, etc...
});

Is a dataset part of a Knowledge Graph

var featureLyer = MapView.Active.Map.GetLayersAsFlattenedList()
                .OfType<FeatureLayer>().FirstOrDefault();
if (featureLyer == null)
  return;

QueuedTask.Run(() =>
{
  //Get the feature class
  var fc = featureLyer.GetFeatureClass();

  // is it part of a KnowledgeGraph?
  var isPartOfKG = fc.GetIsKnowledgeGraphDataset();

});

Get KG Datastore

var kgLayer = MapView.Active.Map.GetLayersAsFlattenedList()
                .OfType<KnowledgeGraphLayer>().FirstOrDefault();
if (kgLayer == null)
  return;

QueuedTask.Run(() =>
{
  // get the datastore
  var kg = kgLayer.GetDatastore();

  // now submit a search or a query
  // kg.SubmitSearch
  // kg.SubmitQuery
});

Get KG Service uri

kgLayer.GetServiceUri();

SubLayers of a KnowledgeGraph Layer

var map = MapView.Active.Map;
var kgLayer = map.GetLayersAsFlattenedList().OfType<KnowledgeGraphLayer>().FirstOrDefault();
if (kgLayer == null)
  return;

if (map.MapType == MapType.LinkChart)
{
  // if map is of MapType.LinkChart then the first level
  // children of the kgLayer are of type LinkChartFeatureLayer
  var childLayers = kgLayer.Layers;
  foreach (var childLayer in childLayers)
  {
    if (childLayer is LinkChartFeatureLayer lcFeatureLayer)
    {
      var isEntity = lcFeatureLayer.IsEntity;
      var isRel = lcFeatureLayer.IsRelationship;

      // TODO - continue processing
    }
  }
}
else if (map.MapType == MapType.Map)
{
  // if map is of MapType.Map then the children of the
  // kgLayer are the standard Featurelayer and StandAloneTable
  var chidlren = kgLayer.GetMapMembersAsFlattenedList();
  foreach (var child in chidlren)
  {
    if (child is FeatureLayer fl)
    {
      // TODO - process the feature layer
    }
    else if (child is StandaloneTable st)
    {
      // TODO - process the standalone table
    }
  }
}

Create a LinkChart from a subset of an existing LinkChart IDSet

var map = MapView.Active.Map;
if (map.MapType != MapType.LinkChart)
  return;

// find the KG layer
var kgLayer = map.GetLayersAsFlattenedList().OfType<KnowledgeGraphLayer>().FirstOrDefault();
if (kgLayer == null)
  return;

// find the first LinkChartFeatureLayer in the KG layer
var lcFeatureLayer = kgLayer.GetLayersAsFlattenedList().OfType<LinkChartFeatureLayer>().FirstOrDefault();
if (lcFeatureLayer != null)
  return;

QueuedTask.Run(() =>
{
  // get the KG
  var kg = kgLayer.GetDatastore();

  // get the ID set of the KG layer
  var idSet = kgLayer.GetIDSet();

  // get the named object type in the KG that the LinkChartFeatureLayer represents
  var typeName = lcFeatureLayer.GetTypeName();
  // if there are items in the ID Set for this type
  if (idSet.Contains(typeName))
  {
    // build a new ID set containing only these records
    var dict = idSet.ToOIDDictionary();
    var oids = dict[typeName];

    var newDict = new Dictionary<string, List<long>>();
    newDict.Add(typeName, oids);

    var newIDSet = KnowledgeGraphLayerIDSet.FromDictionary(kg, newDict);

    // now create a new link chart using just this subset of records
    MapFactory.Instance.CreateLinkChart("subset LinkChart", kg, newIDSet);
  }
});

Graph Query and Text Search

Submit a Graph Query

//On the QueuedTask...
//and assuming you have established a connection to a knowledge graph
//...
//Construct an openCypher query - return the first 10 entities (whatever
//they are...)
var query = "MATCH (n) RETURN n LIMIT 10";//default limit is 100 if not specified
//other examples...
//query = "MATCH (a:Person) RETURN [a.name, a.age] ORDER BY a.age DESC LIMIT 50";
//query = "MATCH (b:Person) RETURN { Xperson: { Xname: b.name, Xage: b.age } } ORDER BY b.name DESC";
//query = "MATCH p = (c:Person)-[:HasCar]-() RETURN p ORDER BY c.name DESC";

//Create a query filter
//Note: OutputSpatialReference is currently ignored
var kg_qf = new KnowledgeGraphQueryFilter()
{
  QueryText = query
};
//Optionally - u can choose to include provenance in the results
//(_if_ the KG has provenance - otherwise the query will fail)
if (includeProvenanceIfPresent)
{
  //see "Get Whether KG Supports Provenance" snippet
  if (KnowledgeGraphSupportsProvenance(kg))
  {
    //Only include if the KG has provenance
    kg_qf.ProvenanceBehavior =
      KnowledgeGraphProvenanceBehavior.Include;//default is exclude
  }
}
//submit the query - returns a KnowledgeGraphCursor
using (var kg_rc = kg.SubmitQuery(kg_qf))
{
  //wait for rows to be returned from the server
  //note the "await"...
  while (await kg_rc.WaitForRowsAsync())
  {
    //Rows have been retrieved - process this "batch"...
    while (kg_rc.MoveNext())
    {
      //Get the current KnowledgeGraphRow
      using (var graph_row = kg_rc.Current)
      {
        //Graph row is an array, process all returned values...
        var val_count = (int)graph_row.GetCount();
        for (int i = 0; i < val_count; i++)
        {
          var retval = graph_row[i];
          //Process row value (note: recursive)
          //See "Process a KnowledgeGraphRow Value" snippet
          ProcessKnowledgeGraphRowValue(retval);
        }
      }
    }
  }//WaitForRowsAsync
}//SubmitQuery

Submit a Text Search

//On the QueuedTask...
//and assuming you have established a connection to a knowledge graph
//...
//Construct a KG search filter. Search text uses Apache Lucene Query Parser
//syntax - https://lucene.apache.org/core/2_9_4/queryparsersyntax.html
var kg_sf = new KnowledgeGraphSearchFilter()
{
  SearchTarget = KnowledgeGraphNamedTypeCategory.Entity,
  SearchText = "Acme Electric Co.",
  ReturnSearchContext = true,
  MaxRowCount = 10 //Default is 100 if not specified
};

//submit the search - returns a KnowledgeGraphCursor
var e = 0;
using (var kg_rc = kg.SubmitSearch(kg_sf))
{
  //wait for rows to be returned from the server
  //note the "await"...
  while (await kg_rc.WaitForRowsAsync())
  {
    //Rows have been retrieved - process this "batch"...
    while (kg_rc.MoveNext())
    {
      //Get the current KnowledgeGraphRow
      using (var graph_row = kg_rc.Current)
      {
        //We are returning entities from this search
        var entity = graph_row[0] as KnowledgeGraphEntityValue;
        var entity_type = entity.GetTypeName();
        var record = new List<string>();
        //discover keys(aka "fields") dynamically via GetKeys
        foreach (var prop_name in entity.GetKeys())
        {
          var obj_val = entity[prop_name] ?? "null";
          record.Add(obj_val.ToString());
        }
        System.Diagnostics.Debug.WriteLine(
          $"{entity_type}[{e++}] " + string.Join(", ", record));
        //or use "Process a KnowledgeGraphRow Value" snippet
        //ProcessKnowledgeGraphRowValue(entity);
      }
    }
  }//WaitForRowsAsync
}//SubmitSearch

Convert an Open Cypher Query Result to a Selection

QueuedTask.Run(async () =>
{
  //Given an open-cypher qry against an entity or relationship type
  var qry = @"MATCH (p:PhoneNumber) RETURN p LIMIT 10";

  //create a KG query filter
  var kg_qry_filter = new KnowledgeGraphQueryFilter()
  {
    QueryText = qry
  };

  //save a list of the ids
  var oids = new List<long>();
  using (var kgRowCursor = kg.SubmitQuery(kg_qry_filter))
  {
    //wait for rows to be returned asynchronously from the server
    while (await kgRowCursor.WaitForRowsAsync())
    {
      //get the rows using "standard" move next
      while (kgRowCursor.MoveNext())
      {
        //current row is accessible via ".Current" prop of the cursor
        using (var graphRow = kgRowCursor.Current)
        {
          var cell_phone = graphRow[0] as KnowledgeGraphEntityValue;
          //note: some user-managed graphs do not have objectids
          oids.Add(cell_phone.GetObjectID());
        }
      }
    }
  }
  //create a query filter using the oids
  if (oids.Count > 0)
  {
    //select them on the layer
    var qf = new QueryFilter()
    {
      ObjectIDs = oids //apply the oids to the ObjectIds property
    };
    //select the child feature layer or standalone table representing
    //the given entity or relate type whose records are to be selected
    var phone_number_fl = kg_layer.GetLayersAsFlattenedList()
        .OfType<FeatureLayer>().First(l => l.Name == "PhoneNumber");

    //perform the selection
    phone_number_fl.Select(qf);
  }
});

Use Bind Parameters with an Open Cypher Query

QueuedTask.Run(async () =>
{
  //assume we have, in this case, a list of ids (perhaps retrieved
  //via a selection, hardcoded (like here), etc.
  var oids = new List<long>() { 3,4,7,8,9,11,12,14,15,19,21,25,29,
      31,32,36,37,51,53,54,55,56,59,63,75,78,80,84,86,88,96,97,98,101,
      103,106};
  //In the query, we refer to the "bind parameter" with the
  //"$" and a variable name - '$object_ids' in this example
  var qry = @"MATCH (p:PhoneNumber) " +
            @" WHERE p.objectid IN $object_ids " +
            @"RETURN p";

  //we provide the values to be substituted for the variable via the
  //KnowledgeGraphQueryFilter BindParameter property...
  var kg_qry_filter = new KnowledgeGraphQueryFilter()
  {
    QueryText = qry
  };
  //the bind parameter added to the query filter must refer to
  //the variable name used in the query string (without the "$")
  //Note:
  //Collections must be converted to a KnowledgeGraphArrayValue before
  //being assigned to a BindParameter
  var kg_oid_array = new KnowledgeGraphArrayValue();
  kg_oid_array.AddRange(oids);
  oids.Clear();

  kg_qry_filter.BindParameters["object_ids"] = kg_oid_array;

  //submit the query
  using (var kgRowCursor = kg.SubmitQuery(kg_qry_filter))
  {
    //wait for rows to be returned asynchronously from the server
    while (await kgRowCursor.WaitForRowsAsync())
    {
      //get the rows using "standard" move next
      while (kgRowCursor.MoveNext())
      {
        //current row is accessible via ".Current" prop of the cursor
        using (var graphRow = kgRowCursor.Current)
        {
          var cell_phone = graphRow[0] as KnowledgeGraphEntityValue;
          var oid = cell_phone.GetObjectID();

          var name = (string)cell_phone["FULL_NAME"];
          var ph_number = (string)cell_phone["PHONE_NUMBER"];
          System.Diagnostics.Debug.WriteLine(
            $"[{oid}] {name}, {ph_number}");
        }
      }
    }
  }
});

Use Bind Parameters with an Open Cypher Query2

QueuedTask.Run(async () =>
{
  //assume we have, in this case, a list of ids (perhaps retrieved
  //via a selection, hardcoded (like here), etc.
  var oids = new List<long>() { 3,4,7,8,9,11,12,14,15,19,21,25,29,
      31,32,36,37,51,53,54,55,56,59,63,75,78,80,84,86,88,96,97,98,101,
      103,106};
  //In the query, we refer to the "bind parameter" with the
  //"$" and a variable name - '$object_ids' and '$sel_geom'
  //in this example
  var qry = @"MATCH (p:PhoneNumber) " +
              @"WHERE p.objectid IN $object_ids AND " +
              @"esri.graph.ST_Intersects($sel_geom, p.shape) " +
              @"RETURN p";
  //create a KG query filter
  var kg_qry_filter = new KnowledgeGraphQueryFilter()
  {
    QueryText = qry
  };

  //the bind parameter added to the query filter must refer to
  //the variable name used in the query string (without the "$")
  //Note:
  //Collections must be converted to a KnowledgeGraphArrayValue before
  //being assigned to a BindParameter
  var kg_oid_array = new KnowledgeGraphArrayValue();
  kg_oid_array.AddRange(oids);
  kg_qry_filter.BindParameters["object_ids"] = kg_oid_array;
  kg_qry_filter.BindParameters["sel_geom"] = poly;
  oids.Clear();

  //submit the query
  using (var kgRowCursor = kg.SubmitQuery(kg_qry_filter))
  {
    //wait for rows to be returned asynchronously from the server
    while (await kgRowCursor.WaitForRowsAsync())
    {
      //get the rows using "standard" move next
      while (kgRowCursor.MoveNext())
      {
        //current row is accessible via ".Current" prop of the cursor
        using (var graphRow = kgRowCursor.Current)
        {
          #region Process Row

          var cell_phone = graphRow[0] as KnowledgeGraphEntityValue;
          var oid = cell_phone.GetObjectID();

          var name = (string)cell_phone["FULL_NAME"];
          var ph_number = (string)cell_phone["PHONE_NUMBER"];
          System.Diagnostics.Debug.WriteLine(
            $"[{oid}] {name}, {ph_number}");

Call WaitForRowsAsync With Cancellation

//On the QueuedTask...
//and assuming you have established a connection to a knowledge graph
//...
//submit query or search to return a KnowledgeGraphCursor
//using (var kg_rc = kg.SubmitQuery(kg_qf)) {
//using (var kg_rc = kg.SubmitSearch(kg_sf)) {
//...
//wait for rows to be returned from the server
//"auto-cancel" after 20 seconds
var cancel = new CancellationTokenSource(new TimeSpan(0, 0, 20));
//catch TaskCanceledException
try
{
  while (await kg_rc.WaitForRowsAsync(cancel.Token))
  {
    //check for row events
    while (kg_rc.MoveNext())
    {
      using (var graph_row = kg_rc.Current)
      {
        //Graph row is an array, process all returned values...
        var val_count = (int)graph_row.GetCount();
        for (int i = 0; i < val_count; i++)
        {
          var retval = graph_row[i];
          //Process row value (note: recursive)
          //See "Process a KnowledgeGraphRow Value" snippet
          ProcessKnowledgeGraphRowValue(retval);
        }
      }
    }
  }
}
//Timeout expired
catch (TaskCanceledException tce)
{
  //Handle cancellation as needed
}
cancel.Dispose();

Process a KnowledgeGraphRow Value

//Base class for entities and relationships
//(including documents and provenance)
public void ProcessGraphNamedObjectValue(
  KnowledgeGraphNamedObjectValue kg_named_obj_val)
{
  if (kg_named_obj_val is KnowledgeGraphEntityValue kg_entity)
  {
    var label = kg_entity.GetLabel();
    //TODO - use label
  }
  else if (kg_named_obj_val is KnowledgeGraphRelationshipValue kg_rel)
  {
    var has_entity_ids = kg_rel.GetHasRelatedEntityIDs();
    if (kg_rel.GetHasRelatedEntityIDs())
    {
      var origin_id = kg_rel.GetOriginID();
      var dest_id = kg_rel.GetDestinationID();
      //TODO - use ids
    }
  }
  var id = kg_named_obj_val.GetID();
  var oid = kg_named_obj_val.GetObjectID();
  //Note: Typename corresponds to the name of the feature class or table
  //in the relational gdb model -and- to the name of the KnowledgeGraphNamedObjectType
  //in the knowledge graph data model
  var type_name = kg_named_obj_val.GetTypeName();
  //TODO use id, object id, etc.
}

//Object values include entities, relationships, and anonymous objects
public void ProcessGraphObjectValue(KnowledgeGraphObjectValue kg_obj_val)
{
  switch (kg_obj_val)
  {
    case KnowledgeGraphEntityValue kg_entity:
      ProcessGraphNamedObjectValue(kg_entity);
      break;
    case KnowledgeGraphRelationshipValue kg_rel:
      ProcessGraphNamedObjectValue(kg_rel);
      break;
    default:
      //Anonymous objects
      break;
  }
  //graph object values have a set of properties (equivalent
  //to a collection of key/value pairs)
  var keys = kg_obj_val.GetKeys();
  foreach (var key in keys)
    ProcessKnowledgeGraphRowValue(kg_obj_val[key]);//Recurse
}

//Process a KnowledgeGraphValue from a query or search
public void ProcessGraphValue(KnowledgeGraphValue kg_val)
{
  switch (kg_val)
  {
    case KnowledgeGraphPrimitiveValue kg_prim:
      //KnowledgeGraphPrimitiveValue not currently used in
      //query and search 
      ProcessKnowledgeGraphRowValue(kg_prim.GetValue());//Recurse
      return;
    case KnowledgeGraphArrayValue kg_array:
      var count = kg_array.GetSize();
      //Recursively process each value in the array
      for (ulong i = 0; i < count; i++)
        ProcessKnowledgeGraphRowValue(kg_array[i]);//Recurse
      return;
    case KnowledgeGraphPathValue kg_path:
      //Entities
      var entity_count = kg_path.GetEntityCount();
      //Recursively process each entity value in the path
      for (ulong i = 0; i < entity_count; i++)
        ProcessGraphObjectValue(kg_path.GetEntity(i));//Recurse

      //Recursively process each relationship value in the path
      var relate_count = kg_path.GetRelationshipCount();
      for (ulong i = 0; i < relate_count; i++)
        ProcessGraphObjectValue(kg_path.GetRelationship(i));//Recurse
      return;
    case KnowledgeGraphObjectValue kg_object:
      ProcessGraphObjectValue(kg_object);//Recurse
      return;
    default:
      var type_string = kg_val.GetType().ToString();
      System.Diagnostics.Debug.WriteLine(
        $"Unknown: '{type_string}'");
      return;
  }
}

//Process each value from the KnowledgeGraphRow array
public void ProcessKnowledgeGraphRowValue(object value)
{
  switch (value)
  {
    //Graph value?
    case KnowledgeGraphValue kg_val:
      var kg_type = kg_val.KnowledgeGraphValueType.ToString();
      System.Diagnostics.Debug.WriteLine(
        $"KnowledgeGraphValue: '{kg_type}'");
      ProcessGraphValue(kg_val);//Recurse
      return;
    //Primitive types...add additional logic as needed
    case System.DBNull dbn:
      System.Diagnostics.Debug.WriteLine("DBNull.Value");
      return;
    case string str:
      System.Diagnostics.Debug.WriteLine($"'{str}' (string)");
      return;
    case long l_val:
      System.Diagnostics.Debug.WriteLine($"{l_val} (long)");
      return;
    case int i_val:
      System.Diagnostics.Debug.WriteLine($"{i_val} (int)");
      return;
    case short s_val:
      System.Diagnostics.Debug.WriteLine($"{s_val} (short)");
      return;
    case double d_val:
      System.Diagnostics.Debug.WriteLine($"{d_val} (double)");
      return;
    case float f_val:
      System.Diagnostics.Debug.WriteLine($"{f_val} (float)");
      return;
    case DateTime dt_val:
      System.Diagnostics.Debug.WriteLine($"{dt_val} (DateTime)");
      return;
    case DateOnly dt_only_val:
      System.Diagnostics.Debug.WriteLine($"{dt_only_val} (DateOnly)");
      return;
    case TimeOnly tm_only_val:
      System.Diagnostics.Debug.WriteLine($"{tm_only_val} (TimeOnly)");
      return;
    case DateTimeOffset dt_tm_offset_val:
      System.Diagnostics.Debug.WriteLine(
        $"{dt_tm_offset_val} (DateTimeOffset)");
      return;
    case System.Guid guid_val:
      var guid_string = guid_val.ToString("B");
      System.Diagnostics.Debug.WriteLine($"'{guid_string}' (Guid)");
      return;
    case Geometry geom_val:
      var geom_type = geom_val.GeometryType.ToString();
      var is_empty = geom_val.IsEmpty;
      var wkid = geom_val.SpatialReference?.Wkid ?? 0;
      System.Diagnostics.Debug.WriteLine(
        $"geometry: {geom_type}, empty: {is_empty}, sr_wkid {wkid} (shape)");
      return;
    default:
      //Blob? Others?
      var type_str = value.GetType().ToString();
      System.Diagnostics.Debug.WriteLine($"Primitive: {type_str}");
      return;
  }
}

// ...submit query or search
//using (var kg_rc = kg.SubmitQuery(kg_qf)) {
//using (var kg_rc = kg.SubmitSearch(kg_sf)) {
//  ...wait for rows ...
//  while (await kg_rc.WaitForRowsAsync()) {
//   ...rows have been retrieved
//   while (kg_rc.MoveNext()) {
//     ...get the current KnowledgeGraphRow
//     using (var graph_row = kg_rc.Current) {
//        var val_count = (int)graph_row.GetCount();
//        for (int i = 0; i<val_count; i++)
//           ProcessKnowledgeGraphRowValue(graph_row[i]);

Link Charts

Find link chart project items

// find all the link chart project items
var linkChartItems = Project.Current.GetItems<MapProjectItem>().Where(pi => pi.MapType == MapType.LinkChart);

// find a link chart project item by name
var linkChartItem = Project.Current.GetItems<MapProjectItem>().FirstOrDefault(pi => pi.Name == "Acme Link Chart");

Find link chart map by name

var projectItem = Project.Current.GetItems<MapProjectItem>().FirstOrDefault(pi => pi.Name == "Acme Link Chart");
var linkChartMap = projectItem?.GetMap();

Does Active MapView contain a link chart map

var mv = MapView.Active;
// check the view
var isLinkChartView = mv.IsLinkChartView;

// or alternatively get the map and check that
var map = MapView.Active.Map;
// check the MapType to determine if it's a link chart map
var isLinkChart = map.MapType == MapType.LinkChart;
// or you could use the following
// var isLinkChart = map.IsLinkChart;

Find Link Chart from Map panes

var mapPanes = FrameworkApplication.Panes.OfType<IMapPane>().ToList();
var mapPane = mapPanes.FirstOrDefault(
    mp => mp.MapView.IsLinkChartView && mp.MapView.Map.Name == "Acme Link Chart");
var lcMap = mapPane.MapView.Map;

Get and set the link chart layout

var mv = MapView.Active;

// a MapView can encapsulate a link chart IF it's map
// is of type MapType.LinkChart
var map = mv.Map;
var isLinkChart = map.MapType == MapType.LinkChart;
// or use the following
// var isLinkChart = map.IsLinkChart;

QueuedTask.Run(() =>
{
  if (isLinkChart)
  {
    // get the layout algorithm
    var layoutAlgorithm = mv.GetLinkChartLayout();

    // toggle the value
    if (layoutAlgorithm == KnowledgeLinkChartLayoutAlgorithm.Geographic_Organic_Standard)
      layoutAlgorithm = KnowledgeLinkChartLayoutAlgorithm.Organic_Standard;
    else
      layoutAlgorithm = KnowledgeLinkChartLayoutAlgorithm.Geographic_Organic_Standard;

    // set it
    mv.SetLinkChartLayoutAsync(layoutAlgorithm);

    // OR set it and force a redraw / update
    // await mv.SetLinkChartLayoutAsync(layoutAlgorithm, true);
  }
});

Create and Append to Link Charts

Create a Link Chart Containing All Records for a KG

QueuedTask.Run(() =>
{
  //Create the link chart and show it
  //build the IDSet using KnowledgeGraphFilterType.AllNamedObjects
  var idSet = KnowledgeGraphLayerIDSet.FromKnowledgeGraph(
    kg, KnowledgeGraphFilterType.AllNamedObjects);
  var linkChart = MapFactory.Instance.CreateLinkChart(
                    "KG Link Chart", kg, idSet);
  FrameworkApplication.Panes.CreateMapPaneAsync(linkChart);
});

Create a Link Chart With an Empty KG Layer

QueuedTask.Run(() =>
{
  //Create the link chart with a -null- id set
  //This will create a KG Layer with empty sub-layers
  //(Note: you cannot create an empty KG layer on a map - only on a link chart)
  var linkChart = MapFactory.Instance.CreateLinkChart(
                    "KG Link Chart", kg, null);
  FrameworkApplication.Panes.CreateMapPaneAsync(linkChart);
});

Create a link chart with all the entities of the Knowledge Graph

string url =
        @"https://acme.server.com/server/rest/services/Hosted/AcmeKnowledgeGraph/KnowledgeGraphServer";

QueuedTask.Run(() =>
{
  using (var kg = new KnowledgeGraph(new KnowledgeGraphConnectionProperties(new Uri(url))))
  {
    var idSet = KnowledgeGraphLayerIDSet.FromKnowledgeGraph(
      kg, KnowledgeGraphFilterType.AllEntities);
    var newLinkChart = MapFactory.Instance.CreateLinkChart(
      "All_Entities link chart", kg, idSet);
  };
});

Create a Link Chart from a query

//use the results of a query to create an idset. Create the link chart
//containing just records corresponding to the query results
var qry = @"MATCH (p1:PhoneNumber)-[r1:MADE_CALL|RECEIVED_CALL]->(c1:PhoneCall)<-" +
          @"[r2:MADE_CALL|RECEIVED_CALL]-(p2:PhoneNumber)-[r3:MADE_CALL|RECEIVED_CALL]" +
          @"->(c2:PhoneCall)<-[r4:MADE_CALL|RECEIVED_CALL]-(p3:PhoneNumber) " +
          @"WHERE p1.FULL_NAME = ""Robert Johnson"" AND " +
          @"p3.FULL_NAME= ""Dan Brown"" AND " +
          @"p1.globalid <> p2.globalid AND " +
          @"p2.globalid <> p3.globalid " +
          @"RETURN p1, r1, c1, r2, p2, r3, c2, r4, p3";

var dict = new Dictionary<string, List<long>>();

QueuedTask.Run(async () =>
{
  using (var kg = kg_layer.GetDatastore())
  {
    var graphQuery = new KnowledgeGraphQueryFilter()
    {
      QueryText = qry
    };

    using (var kgRowCursor = kg.SubmitQuery(graphQuery))
    {
      while (await kgRowCursor.WaitForRowsAsync())
      {
        while (kgRowCursor.MoveNext())
        {
          using (var graphRow = kgRowCursor.Current)
          {
            // process the row
            var cnt_val = (int)graphRow.GetCount();
            for (int v = 0; v < cnt_val; v++)
            {
              var obj_val = graphRow[v] as KnowledgeGraphNamedObjectValue;
              var type_name = obj_val.GetTypeName();
              var oid = (long)obj_val.GetObjectID();
              if (!dict.ContainsKey(type_name))
              {
                dict[type_name] = new List<long>();
              }
              if (!dict[type_name].Contains(oid))
                dict[type_name].Add(oid);
            }
          }
        }
      }
    }
    //make an ID Set to create the LinkChart
    var idSet = KnowledgeGraphLayerIDSet.FromDictionary(kg, dict);

    //Create the link chart and show it
    var linkChart = MapFactory.Instance.CreateLinkChart(
                      "KG With ID Set", kg, idSet);
    FrameworkApplication.Panes.CreateMapPaneAsync(linkChart);
  }
});

Create a link chart based on a template link chart

// note that the template link chart MUST use the same KG server

string url =
        @"https://acme.server.com/server/rest/services/Hosted/AcmeKnowledgeGraph/KnowledgeGraphServer";

QueuedTask.Run(() =>
{
  // find the existing link chart by name
  var projectItem = Project.Current.GetItems<MapProjectItem>()
  .FirstOrDefault(pi => pi.Name == "Acme Link Chart");
  var linkChartMap = projectItem?.GetMap();
  if (linkChartMap == null)
    return;

  //Create a connection properties
  var kg_props =
      new KnowledgeGraphConnectionProperties(new Uri(url));
  try
  {
    //Open a connection
    using (var kg = new KnowledgeGraph(kg_props))
    {
      //Add all entities to the link chart
      var idSet = KnowledgeGraphLayerIDSet.FromKnowledgeGraph(
            kg, KnowledgeGraphFilterType.AllEntities);
      //Create the new link chart and show it
      var newLinkChart = MapFactory.Instance.CreateLinkChart(
                        "KG from Template", kg, idSet, linkChartMap.URI);
      FrameworkApplication.Panes.CreateMapPaneAsync(newLinkChart);
    }
  }
  catch (Exception ex)
  {
    System.Diagnostics.Debug.WriteLine(ex.ToString());
  }
});

Checking KnowledgeGraphLayerException

// running on QueuedTask

var dict = new Dictionary<string, List<long>>();
dict.Add("person", new List<long>());  //Empty list means all records
dict.Add("made_call", null);  //null list means all records

// or specific records - however the ids are obtained
dict.Add("phone_call", new List<long>() { 1, 5, 18, 36, 78 });

// make the id set
var idSet = KnowledgeGraphLayerIDSet.FromDictionary(kg, dict);

try
{
  //Create the link chart and show it
  var linkChart = MapFactory.Instance.CreateLinkChart(
                    "KG With ID Set", kg, idSet);
  FrameworkApplication.Panes.CreateMapPaneAsync(linkChart);
}
catch (KnowledgeGraphLayerException e)
{
  // get the invalid named types
  //   remember that the named types are case-sensitive
  var invalidNamedTypes = e.InvalidNamedTypes;

  // do something with the invalid named types 
  // for example - log or return to caller to show message to user
}

Append to Link Chart

//We create an id set to contain the records to be appended
var dict = new Dictionary<string, List<long>>();
dict["Suspects"] = new List<long>();

//In this case, via results from a query...
var qry2 = "MATCH (s:Suspects) RETURN s";

QueuedTask.Run(async () =>
{
  using (var kg = kg_layer.GetDatastore())
  {
    var graphQuery = new KnowledgeGraphQueryFilter()
    {
      QueryText = qry2
    };

    using (var kgRowCursor = kg.SubmitQuery(graphQuery))
    {
      while (await kgRowCursor.WaitForRowsAsync())
      {
        while (kgRowCursor.MoveNext())
        {
          using (var graphRow = kgRowCursor.Current)
          {
            var obj_val = graphRow[0] as KnowledgeGraphNamedObjectValue;
            var oid = (long)obj_val.GetObjectID();
            dict["Suspects"].Add(oid);
          }
        }
      }
    }

    //make an ID Set to append to the LinkChart
    var idSet = KnowledgeGraphLayerIDSet.FromDictionary(kg, dict);
    //Get the relevant link chart to which records will be
    //appended...in this case, from an open map pane in the
    //Pro application...
    var mapPanes = FrameworkApplication.Panes.OfType<IMapPane>().ToList();
    var mapPane = mapPanes.First(
      mp => mp.MapView.IsLinkChartView &&
      mp.MapView.Map.Name == "Acme Link Chart");
    var linkChartMap = mapPane.MapView.Map;

    //or get the link chart from an item in the catalog...etc.,etc.
    //var projectItem = Project.Current.GetItems<MapProjectItem>()
    //      .FirstOrDefault(pi => pi.Name == "Acme Link Chart");
    //var linkChartMap = projectItem?.GetMap();

    //Call AppendToLinkChart with the id set
    if (linkChartMap.CanAppendToLinkChart(idSet))
      linkChartMap.AppendToLinkChart(idSet);
  }
});

ID Sets

Get the ID Set of a KG layer

QueuedTask.Run(() =>
{
  var idSet = kgLayer.GetIDSet();

  // is the set empty?
  var isEmpty = idSet.IsEmpty;
  // get the count of named object types
  var countNamedObjects = idSet.NamedObjectTypeCount;
  // does it contain the entity "Species";
  var contains = idSet.Contains("Species");

  // get the idSet as a dictionary of namedObjectType and oids
  var oidDict = idSet.ToOIDDictionary();
  var speciesOIDs = oidDict["Species"];

  // alternatively get the idSet as a dictionary of 
  // namedObjectTypes and uuids
  var uuidDict = idSet.ToUIDDictionary();
  var speciesUuids = uuidDict["Species"];

});

Create an ID set from a SelectionSet

QueuedTask.Run(() =>
{
  // get the selection set
  var sSet = map.GetSelection();

  // translate to an KnowledgeGraphLayerIDSet
  //  if the selectionset does not contain any KG entity or relationship records
  //    then idSet will be null  
  var idSet = KnowledgeGraphLayerIDSet.FromSelectionSet(sSet);
  if (idSet == null)
    return;


  // you can use the idSet to create a new linkChart
  //   (using MapFactory.Instance.CreateLinkChart)
});

Root Nodes

Toggle Root Node Display

var val = MapView.Active.GetShowRootNodes();

await QueuedTask.Run(() =>
{
  MapView.Active.SetShowRootNodes(!val);
});

Get records that are set as Root Nodes

await QueuedTask.Run(() =>
{
  MapMemberIDSet rootNodes = MapView.Active.GetRootNodes();
  var rootNodeDict = rootNodes.ToDictionary();

  // rootNodeDict is a Dictionary<MapMember, List<long>>

  // access a particular mapMember in the Dictionary
  if (rootNodeDict.ContainsKey(mapMember))
  {
    var oids = rootNodeDict[mapMember];
  }

  // OR iterate through the dictionary
  foreach (var (mm, oids) in rootNodeDict)
  {
    // do something
  }
});

Assign a set of records as Root Nodes

await QueuedTask.Run(() =>
{
  var dict = new Dictionary<MapMember, List<long>>();
  dict.Add(entityLayer, oids);
  MapMemberIDSet mmIDSet = MapMemberIDSet.FromDictionary(dict);

  MapView.Active.SetRootNodes(mmIDSet);
});

Assign a selection as Root Nodes

await QueuedTask.Run(() =>
{
  var mapSel = MapView.Active.Map.GetSelection();

  MapView.Active.SetRootNodes(mapSel);
});

Select the records that are Root Node

await QueuedTask.Run(() =>
{
  var mapSel = MapView.Active.SelectAllRootNodes();


  // this is the same as 
  MapMemberIDSet rootNodes = MapView.Active.GetRootNodes();
  SelectionSet selSet = SelectionSet.FromMapMemberIDSet(rootNodes);
  MapView.Active.Map.SetSelection(selSet);
});

Define and select a set of records as Root Nodes

await QueuedTask.Run(() =>
{
  var dict = new Dictionary<MapMember, List<long>>();
  dict.Add(entityLayer, oids);
  dict.Add(entityLayer2, oids2);
  MapMemberIDSet mmIDSet = MapMemberIDSet.FromDictionary(dict);

  MapView.Active.SelectRootNodes(mmIDSet);
});

Clear Root Nodes

await QueuedTask.Run(() =>
{
  MapView.Active.ClearRootNodes();
});

Editing

Create a new Entity

await QueuedTask.Run(() =>
{

  //Instantiate an operation for the Create
  var edit_op = new EditOperation()
  {
    Name = "Create a new organization",
    SelectNewFeatures = true
  };

  //Use datasets or feature layer(s) or standalone table(s)
  //Get a reference to the KnowledgeGraph
  //var kg = ... ; 

  //Open the feature class or Table to be edited
  var org_fc = kg.OpenDataset<FeatureClass>("Organization");

  //Alternatively, use the feature layer for 'Organization' if your context is a map
  //Get the parent KnowledgeGraphLayer
  var kg_layer = mv.Map.GetLayersAsFlattenedList()?
                .OfType<ArcGIS.Desktop.Mapping.KnowledgeGraphLayer>().First();
  //From the KG Layer get the relevant child feature layer
  var org_fl = kg_layer.GetLayersAsFlattenedList().OfType<FeatureLayer>()
                  .First(child_layer => child_layer.Name == "Organization");

  //Define attributes
  var attribs = new Dictionary<string, object>();
  attribs["Name"] = "Acme Ltd.";
  attribs["Description"] = "Specializes in household items";
  attribs["SHAPE"] = org_location;

  //Add it to the operation via the dataset...
  edit_op.Create(org_fc, attribs);
  //or use the feature layer/stand alone table if preferred and available
  //edit_op.Create(org_fl, attribs);

  if (edit_op.Execute())
  {
    //TODO: Operation succeeded
  }

});

Create a new Relationship from Existing Entities 1

var create_rel = await QueuedTask.Run(() =>
{
  //Instantiate an operation for the Create
  var edit_op = new EditOperation()
  {
    Name = "Create a new relationship record",
    SelectNewFeatures = true
  };

  //Use datasets or feature layer(s) or standalone table(s)
  //Get a reference to the KnowledgeGraph
  //var kg = ... ; 

  //We will use a relate called 'HasEmployee' to relate an Organization w/ a Person
  //Use either tables or map members to get the rows to be related...
  var org_fc = kg.OpenDataset<FeatureClass>("Organization");
  var person_tbl = kg.OpenDataset<Table>("Person");

  //Get the relationship dataset
  //We can use either a table or standalone table
  var emp_tbl = kg.OpenDataset<Table>("HasEmployee");

  //we need the names of the origin and destination relationship properties
  var kg_prop_info = kg.GetPropertyNameInfo();

  //Arbitrarily use the first record from the two entity datasets "to be" related
  //Entities are always related by Global ID. Origin to Destination specifies the
  //direction (of the relate).
  //
  //Populate the attributes for the relationship
  var attribs = new Dictionary<string, object>();

  using (var rc = org_fc.Search())
  {
    if (rc.MoveNext())
      //Use the KnowledgeGraphPropertyInfo to avoid hardcoding...
      attribs[kg_prop_info.OriginIDPropertyName] = rc.Current.GetGlobalID();
  }
  using (var rc = person_tbl.Search())
  {
    if (rc.MoveNext())
      //Use the KnowledgeGraphPropertyInfo to avoid hardcoding...
      attribs[kg_prop_info.DestinationIDPropertyName] = rc.Current.GetGlobalID();
  }

  //Add any extra attribute information for the relation as needed
  attribs["StartDate"] = new DateTimeOffset(DateTime.Now);

  //Add a create for the relationship to the operation
  edit_op.Create(emp_tbl, attribs);

  //Do the create
  return edit_op.Execute();
});

Create a new Relationship from Existing Entities 2

var create_rel2 = await QueuedTask.Run(() =>
{
  //Instantiate an operation for the Create
  var edit_op = new EditOperation()
  {
    Name = "Create a new relationship record",
    SelectNewFeatures = true
  };

  //Use datasets or feature layer(s) or standalone table(s)
  //Get a reference to the KnowledgeGraph
  //var kg = ... ; 

  //We will use a relate called 'HasEmployee' to relate an Organization w/ a Person
  //Use either tables or map members to get the rows to be related...
  var org_fc = kg.OpenDataset<FeatureClass>("Organization");
  var person_tbl = kg.OpenDataset<Table>("Person");

  //Get the relationship dataset
  //We can use either a table or standalone table
  var emp_tbl = kg.OpenDataset<Table>("HasEmployee");

  // get the origin, destination records
  Guid guidOrigin = Guid.Empty;
  Guid guidDestination = Guid.Empty;
  using (var rc = org_fc.Search())
  {
    if (rc.MoveNext())
      //Use the KnowledgeGraphPropertyInfo to avoid hardcoding...
      guidOrigin = rc.Current.GetGlobalID();
  }
  using (var rc = person_tbl.Search())
  {
    if (rc.MoveNext())
      //Use the KnowledgeGraphPropertyInfo to avoid hardcoding...
      guidDestination = rc.Current.GetGlobalID();
  }

  //Add any extra attribute information for the relation as needed
  var attribs = new Dictionary<string, object>();
  attribs["StartDate"] = new DateTimeOffset(DateTime.Now);

  var rd = new KnowledgeGraphRelationshipDescription(guidOrigin, guidDestination, attribs);
  //Add a create for the relationship to the operation
  edit_op.Create(emp_tbl, rd);

  //Do the create
  return edit_op.Execute();
});

Create a new Relationship and New Entities 1

var create_rel1 = await QueuedTask.Run(() =>
{
  //This example uses a chained edit operation
  var edit_op = new EditOperation()
  {
    Name = "Create entities and a relationship",
    SelectNewFeatures = true
  };

  //We are just going to use the GDB objects in this one but
  //we could equally use feature layers/standalone tables

  //using Feature Class/Tables (works within Investigation or map)
  var org_fc = kg.OpenDataset<FeatureClass>("Organization");
  var person_tbl = kg.OpenDataset<Table>("Person");
  //Relationship table
  var emp_tbl = kg.OpenDataset<Table>("HasEmployee");

  var attribs = new Dictionary<string, object>();

  //New Organization
  attribs["Name"] = "Acme Ltd.";
  attribs["Description"] = "Specializes in household items";
  attribs["SHAPE"] = org_location;

  //Add it to the operation - we need the rowtoken
  var rowtoken = edit_op.Create(org_fc, attribs);

  attribs.Clear();//we are going to re-use the dictionary

  //New Person
  attribs["Name"] = "Bob";
  attribs["Age"] = "41";
  attribs["Skills"] = "Plumbing, Framing, Flooring";

  //Add it to the operation
  var rowtoken2 = edit_op.Create(person_tbl, attribs);

  attribs.Clear();

  //At this point we must execute the create of the entities
  if (edit_op.Execute())
  {
    //if we are here, the create of the entities was successful

    //Next, "chain" a second create for the relationship - this ensures that
    //Both creates (entities _and_ relation) will be -undone- together if needed
    //....in other words they will behave as if they are a -single- transaction
    var edit_op_rel = edit_op.CreateChainedOperation();

    //we need the names of the origin and destination relation properties
    var kg_prop_info = kg.GetPropertyNameInfo();
    //use the row tokens we held on to from the entity creates
    attribs[kg_prop_info.OriginIDPropertyName] = rowtoken.GlobalID;
    attribs[kg_prop_info.DestinationIDPropertyName] = rowtoken2.GlobalID;

    //Add any extra attribute information for the relation as needed
    attribs["StartDate"] = new DateTimeOffset(DateTime.Now);

    //Do the create of the relate
    edit_op_rel.Create(emp_tbl, attribs);
    return edit_op_rel.Execute();
  }
  return false;//Create of entities failed
});

Create a new Relationship and New Entities 2

var createRel = await QueuedTask.Run(() =>
{
  //This example uses a KnowledgeGraphRelationshipDescription
  var edit_op = new EditOperation()
  {
    Name = "Create entities and a relationship using a KG relate desc",
    SelectNewFeatures = true
  };

  //We are just going to use mapmembers in this example
  //we could equally use feature classes/tables
  var kg_layer = mv.Map.GetLayersAsFlattenedList()?
                .OfType<ArcGIS.Desktop.Mapping.KnowledgeGraphLayer>().First();
  //From the KG Layer get the relevant child feature layer(s) and/or standalone
  //table(s)
  var org_fl = kg_layer.GetLayersAsFlattenedList().OfType<FeatureLayer>()
                  .First(child_layer => child_layer.Name == "Organization");

  var person_stbl = kg_layer.GetStandaloneTablesAsFlattenedList()
                  .First(child_layer => child_layer.Name == "Person");

  var rel_stbl = kg_layer.GetStandaloneTablesAsFlattenedList()
                  .First(child_layer => child_layer.Name == "HasEmployee");

  var attribs = new Dictionary<string, object>();

  //New Organization
  attribs["Name"] = "Acme Ltd.";
  attribs["Description"] = "Specializes in household items";
  attribs["SHAPE"] = org_location;

  //Add it to the operation - we need the rowtoken
  var rowtoken_org = edit_op.Create(org_fl, attribs);

  attribs.Clear();//we are going to re-use the dictionary

  //New Person
  attribs["Name"] = "Bob";
  attribs["Age"] = "41";
  attribs["Skills"] = "Plumbing, Framing, Flooring";

  //Add it to the operation
  var rowtoken_person = edit_op.Create(person_stbl, attribs);

  attribs.Clear();

  //Create the new relationship using a KnowledgeGraphRelationshipDescription
  //Row handles act as the placeholders for the TO BE created new entities that will
  //be related
  var src_row_handle = new RowHandle(rowtoken_org);
  var dest_row_handle = new RowHandle(rowtoken_person);

  //Add any extra attribute information for the relation as needed
  attribs["StartDate"] = new DateTimeOffset(DateTime.Now);

  var rel_desc = new KnowledgeGraphRelationshipDescription(
                              src_row_handle, dest_row_handle, attribs);

  //Add the relate description to the edit operation
  edit_op.Create(rel_stbl, rel_desc);

  //Execute the create of the entities and relationship
  return edit_op.Execute();
});

Create a Provenance Record

await QueuedTask.Run(() =>
{

  //Instantiate an operation for the Create
  var edit_op = new EditOperation()
  {
    Name = "Create a new provenance record",
    SelectNewFeatures = true
  };

  //lets get the provenance table (provenance is not added to the
  //map TOC)
  var provenance_tbl = kg.OpenDataset<Table>("Provenance");
  if (provenance_tbl == null)
    return;
  //we will add a row to the provenance for person entity
  var person_tbl = kg.OpenDataset<Table>("Person");

  //Arbitrarily retrieve the first "person" row
  var instance_id = Guid.Empty;
  using (var rc = person_tbl.Search())
  {
    if (!rc.MoveNext())
      return;
    instance_id = rc.Current.GetGlobalID();//Get the global id
  }

  //Define the provenance attributes - we need the names
  //of the provenance properties from the KG ProvenancePropertyInfo
  var kg_prop_info = kg.GetPropertyNameInfo();
  var attribs = new Dictionary<string, object>();
  var ppi = kg_prop_info.ProvenancePropertyInfo;

  attribs[ppi.ProvenanceTypeNamePropertyName] =
      person_tbl.GetDefinition().GetName();//entity type name
  attribs[ppi.ProvenanceFieldNamePropertyName] = "name";//Must be a property/field on the entity
  attribs[ppi.ProvenanceSourceNamePropertyName] = "Annual Review 2024";//can be anything - can be null
  //note: Source type is controlled by the CodedValueDomain "esri__provenanceSourceType"
  attribs[ppi.ProvenanceSourceTypePropertyName] = "Document";//one of ["Document", "String", "URL"].
  attribs[ppi.ProvenanceSourcePropertyName] = "HR records";//can be anything, not null
  attribs[ppi.ProvenanceCommentPropertyName] = "Rock star";//can be anything - can be null

  //Add in the id of the provenance owner - our "person" in this case
  attribs[ppi.ProvenanceInstanceIDPropertyName] = instance_id;

  //Specify any additional custom attributes added to the provenance
  //schema by the user as needed....
  //attribs["custom_attrib"] = "Foo";
  //attribs["custom_attrib2"] = "Bar";

  //Create the provenance row
  edit_op.Create(provenance_tbl, attribs);
  if (edit_op.Execute())
  {
    //TODO: Operation succeeded
  }

});

Create a Document Record

internal static string GetDocumentTypeName(KnowledgeGraphDataModel kg_dm)
{
  var entity_types = kg_dm.GetEntityTypes();
  foreach (var entity_type in entity_types)
  {
    var role = entity_type.Value.GetRole();
    if (role == KnowledgeGraphNamedObjectTypeRole.Document)
      return entity_type.Value.GetName();
  }
  return "";
}

internal static string GetHasDocumentTypeName(KnowledgeGraphDataModel kg_dm)
{
  var rel_types = kg_dm.GetRelationshipTypes();
  foreach (var rel_type in rel_types)
  {
    var role = rel_type.Value.GetRole();
    if (role == KnowledgeGraphNamedObjectTypeRole.Document)
      return rel_type.Value.GetName();
  }
  return "";
}

internal async void AddDocumentRecord()
{

  await QueuedTask.Run(() =>
  {
    using (var kg = GetKnowledgeGraph())
    {
      var edit_op = new EditOperation()
      {
        Name = "Create Document Example",
        SelectNewFeatures = true
      };

      var doc_entity_name = GetDocumentTypeName(kg.GetDataModel());
      if (string.IsNullOrEmpty(doc_entity_name))
        return false;
      var hasdoc_rel_name = GetHasDocumentTypeName(kg.GetDataModel());
      if (string.IsNullOrEmpty(hasdoc_rel_name))
        return false;

      //Document can also be FeatureClass
      var doc_tbl = kg.OpenDataset<Table>(doc_entity_name);
      var doc_rel_tbl = kg.OpenDataset<Table>(hasdoc_rel_name);

      //This is the document to be added...file, image, resource, etc.
      var url = @"E:\Data\Temp\HelloWorld.txt";
      var text = System.IO.File.ReadAllText(url);

      //Set document properties
      var attribs = new Dictionary<string, object>();
      attribs["contentType"] = @"text/plain";
      attribs["name"] = System.IO.Path.GetFileName(url);
      attribs["url"] = url;
      //Add geometry if relevant
      //attribs["Shape"] = doc_location;

      //optional
      attribs["fileExtension"] = System.IO.Path.GetExtension(url);
      attribs["text"] = System.IO.File.ReadAllText(url);

      //optional and arbitrary - your choice
      attribs["title"] = System.IO.Path.GetFileNameWithoutExtension(url);
      attribs["keywords"] = @"text,file,example";
      attribs["metadata"] = "";

      //Specify any additional custom attributes added to the document
      //schema by the user as needed....
      //attribs["custom_attrib"] = "Foo";
      //attribs["custom_attrib2"] = "Bar";

      //Get the entity whose document this is...
      var org_fc = kg.OpenDataset<FeatureClass>("Organization");
      var qf = new QueryFilter()
      {
        WhereClause = "name = 'Acme'",
        SubFields = "*"
      };
      var origin_org_id = Guid.Empty;
      using (var rc = org_fc.Search(qf))
      {
        if (!rc.MoveNext())
          return false;
        origin_org_id = rc.Current.GetGlobalID();//For the relate
      }

      //Create the document row/feature
      var rowtoken = edit_op.Create(doc_tbl, attribs);
      if (edit_op.Execute())
      {
        //Create the relationship row
        attribs.Clear();
        //we need the names of the origin and destination relation properties
        var kg_prop_info = kg.GetPropertyNameInfo();
        //Specify the origin entity (i.e. the document 'owner') and
        //the document being related to (i.e. the document 'itself')
        attribs[kg_prop_info.OriginIDPropertyName] = origin_org_id;//entity
        attribs[kg_prop_info.DestinationIDPropertyName] = rowtoken.GlobalID;//document

        //Specify any custom attributes added to the has document
        //schema by the user as needed....
        //attribs["custom_attrib"] = "Foo";
        //attribs["custom_attrib2"] = "Bar";

        //"Chain" a second create for the relationship - this ensures that
        //Both creates (doc _and_ "has doc" relation) will be -undone- together if needed
        //....in other words they will behave as if they are a -single- transaction
        var edit_op_rel = edit_op.CreateChainedOperation();
        edit_op_rel.Create(doc_rel_tbl, attribs);
        return edit_op_rel.Execute();
      }
    }
    return false;
  });
}

Modify an Entity and Relationship record

await QueuedTask.Run(() =>
{

  var edit_op = new EditOperation()
  {
    Name = "Modify an Entity and Relationship record",
    SelectModifiedFeatures = true
  };

  //We are  going to use mapmembers in this example
  //we could equally use feature classes/tables
  var kg_layer = mv.Map.GetLayersAsFlattenedList()?
                .OfType<ArcGIS.Desktop.Mapping.KnowledgeGraphLayer>().First();
  //Entity
  var org_fl = kg_layer.GetLayersAsFlattenedList().OfType<FeatureLayer>()
                  .First(child_layer => child_layer.Name == "Organization");
  //and/or Relationship
  var rel_stbl = kg_layer.GetStandaloneTablesAsFlattenedList()
                  .First(child_layer => child_layer.Name == "HasEmployee");

  //Get the entity feature to modify
  long org_oid = -1;
  var org_gid = Guid.Empty;
  var qf = new QueryFilter()
  {
    WhereClause = "name = 'Acme'",
    SubFields = "*"
  };
  using (var rc = org_fl.Search(qf))
  {
    if (!rc.MoveNext())
      return;
    org_oid = rc.Current.GetObjectID();
    org_gid = rc.Current.GetGlobalID();
  }
  if (org_oid == -1)
    return; //nothing to modify

  var attribs = new Dictionary<string, object>();

  //Specify attributes to be updated
  attribs["Name"] = "Acme Ltd.";
  attribs["Description"] = "Specializes in household items";
  attribs["SHAPE"] = org_updated_location;

  //Add to the edit operation
  edit_op.Modify(org_fl, org_oid, attribs);

  //Get the relationship record (if a relate is being updated)
  //we need the name of the origin id property
  var kg_prop_info = kg.GetPropertyNameInfo();
  var sql = $"{kg_prop_info.OriginIDPropertyName} = ";
  sql += "'" + org_gid.ToString("B").ToUpper() + "'";

  qf = new QueryFilter()
  {
    WhereClause = sql,
    SubFields = "*"
  };
  long rel_oid = -1;
  using (var rc = rel_stbl.Search(qf))
  {
    if (!rc.MoveNext())
      return;
    rel_oid = rc.Current.GetObjectID();
  }
  if (rel_oid > -1)
  {
    //add the relate row updates to the edit operation
    attribs.Clear();//we are going to re-use the dictionary
    attribs["StartDate"] = new DateTimeOffset(DateTime.Now);
    attribs["custom_attrib"] = "Foo";
    attribs["custom_attrib2"] = "Bar";
    //Add to the edit operation
    edit_op.Modify(rel_stbl, rel_oid, attribs);
  }
  //do the update(s)
  if (edit_op.Execute())
  {
    //TODO: Operation succeeded
  }

});

Delete an Entity record

await QueuedTask.Run(() =>
{

  var edit_op = new EditOperation()
  {
    Name = "Delete an Entity record"
  };

  //We are  going to use mapmembers in this example
  //we could equally use feature classes/tables
  var kg_layer = mv.Map.GetLayersAsFlattenedList()?
                .OfType<ArcGIS.Desktop.Mapping.KnowledgeGraphLayer>().First();
  //Entity
  var org_fl = kg_layer.GetLayersAsFlattenedList().OfType<FeatureLayer>()
                  .First(child_layer => child_layer.Name == "Organization");

  //Get the entity feature(s) to delete
  long org_oid = -1;
  var qf = new QueryFilter()
  {
    WhereClause = "name = 'Acme'",
    SubFields = "*"
  };
  using (var rc = org_fl.Search(qf))
  {
    if (!rc.MoveNext())
      return;//nothing to delete
    org_oid = rc.Current.GetObjectID();
  } 

  edit_op.Delete(org_fl, org_oid);
  edit_op.Execute();//Do the delete
});

Delete a Relationship record 1

await QueuedTask.Run(() =>
{

  var edit_op = new EditOperation()
  {
    Name = "Delete a Relationship record"
  };

  //We are  going to use mapmembers in this example
  //we could equally use feature classes/tables
  var kg_layer = mv.Map.GetLayersAsFlattenedList()?
                .OfType<ArcGIS.Desktop.Mapping.KnowledgeGraphLayer>().First();
  //Relationship
  var rel_stbl = kg_layer.GetStandaloneTablesAsFlattenedList()
                  .First(child_layer => child_layer.Name == "HasEmployee");

  //Get the relation row to delete
  long rel_oid = -1;
  using (var rc = rel_stbl.Search())
  {
    if (!rc.MoveNext())
      return;
    //arbitrarily, in this example, get the first row
    rel_oid = rc.Current.GetObjectID();
  }

  edit_op.Delete(rel_stbl, rel_oid);
  edit_op.Execute();//Do the delete
});

Delete a Relationship record 2

await QueuedTask.Run(() =>
{

  var edit_op = new EditOperation()
  {
    Name = "Delete a Relationship record"
  };

  //We are  going to use mapmembers in this example
  //we could equally use feature classes/tables
  var kg_layer = mv.Map.GetLayersAsFlattenedList()?
                .OfType<ArcGIS.Desktop.Mapping.KnowledgeGraphLayer>().First();

  //entities
  var entityOrg = kg_layer.GetStandaloneTablesAsFlattenedList()
                  .First(child_layer => child_layer.Name == "Organization");
  var entityPerson = kg_layer.GetStandaloneTablesAsFlattenedList()
                  .First(child_layer => child_layer.Name == "Person");

  //Relationship
  var rel_stbl = kg_layer.GetStandaloneTablesAsFlattenedList()
                  .First(child_layer => child_layer.Name == "HasEmployee");

  // get the origin, destination records
  Guid guidOrigin = Guid.Empty;
  Guid guidDestination = Guid.Empty;
  using (var rc = entityOrg.Search())
  {
    if (rc.MoveNext())
      //Use the KnowledgeGraphPropertyInfo to avoid hardcoding...
      guidOrigin = rc.Current.GetGlobalID();
  }
  using (var rc = entityPerson.Search())
  {
    if (rc.MoveNext())
      //Use the KnowledgeGraphPropertyInfo to avoid hardcoding...
      guidDestination = rc.Current.GetGlobalID();
  }

  var rd = new KnowledgeGraphRelationshipDescription(guidOrigin, guidDestination);
  edit_op.Delete(rel_stbl, rd);
  edit_op.Execute();//Do the delete
});

Schema Edits

Create Entity and Relationship Types with SchemaBuilder

await QueuedTask.Run(() =>
{
  using (var kg = GetKnowledgeGraph())
  {
    if (kg == null)
      return;

    var entity_name = "PhoneCall";
    var relate_name = "WhoCalledWho";

    //Entity Fields
    var descs1 =
        new List<KnowledgeGraphPropertyDescription>();
    descs1.Add(
      new KnowledgeGraphPropertyDescription("PhoneOwner", FieldType.String));
    descs1.Add(
      new KnowledgeGraphPropertyDescription("PhoneNumber", FieldType.String));
    descs1.Add(
      new KnowledgeGraphPropertyDescription("LocationID", FieldType.BigInteger));
    descs1.Add(
      new KnowledgeGraphPropertyDescription("DateAndTime", FieldType.Date));

    //Relate Fields
    var descs2 =
        new List<KnowledgeGraphPropertyDescription>();
    descs2.Add(
      new KnowledgeGraphPropertyDescription("Foo", FieldType.String));
    descs2.Add(
      new KnowledgeGraphPropertyDescription("Bar", FieldType.String));


    var includeShape = true;//change to false to omit the shape column
    var hasZ = false;
    var hasM = false;

    KnowledgeGraphEntityTypeDescription entityDesc = null;
    KnowledgeGraphRelationshipTypeDescription relateDesc = null;
    if (includeShape)
    {
      var sr = kg.GetSpatialReference();
      var shp_desc = new ShapeDescription(GeometryType.Point, sr)
      {
        HasM = hasM,
        HasZ = hasZ
      };
      entityDesc = new KnowledgeGraphEntityTypeDescription(
        entity_name, descs1, shp_desc);
      relateDesc = new KnowledgeGraphRelationshipTypeDescription(
        relate_name, descs2, shp_desc);
    }
    else
    {
      entityDesc = new KnowledgeGraphEntityTypeDescription(
        entity_name, descs1);
      relateDesc = new KnowledgeGraphRelationshipTypeDescription(
        relate_name, descs2);
    }
    //Run the schema builder
    try
    {
      SchemaBuilder sb = new(kg);
      sb.Create(entityDesc);
      sb.Create(relateDesc);
      //Use the KnowledgeGraph extension method 'ApplySchemaEdits(...)'
      //to refresh the Pro UI
      if (!kg.ApplySchemaEdits(sb))
      {
        var err_msg = string.Join(",", sb.ErrorMessages.ToArray());
        System.Diagnostics.Debug.WriteLine($"Entity/Relate Create error: {err_msg}");
      }
    }
    catch (Exception ex)
    {
      System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
  }
});

Delete Entity and Relationship Types with SchemaBuilder

await QueuedTask.Run(() =>
{
  using (var kg = GetKnowledgeGraph())
  {
    if (kg == null)
      return;

    var entity_name = "PhoneCall";
    var relate_name = "WhoCalledWho";

    var entityDesc = new KnowledgeGraphEntityTypeDescription(entity_name);
    var relateDesc = new KnowledgeGraphRelationshipTypeDescription(relate_name);

    //Run the schema builder
    try
    {
      SchemaBuilder sb = new(kg);
      sb.Delete(entityDesc);
      sb.Delete(relateDesc);
      //Use the KnowledgeGraph extension method 'ApplySchemaEdits(...)'
      //to refresh the Pro UI
      if (!kg.ApplySchemaEdits(sb))
      {
        var err_msg = string.Join(",", sb.ErrorMessages.ToArray());
        System.Diagnostics.Debug.WriteLine($"Entity/Relate Delete error: {err_msg}");
      }
    }
    catch (Exception ex)
    {
      System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
  }
});

Modify Entity and Relationship Type Schemas with SchemaBuilder

await QueuedTask.Run(() =>
{
  using (var kg = GetKnowledgeGraph())
  {
    if (kg == null)
      return;

    var entity_name = "PhoneCall";
    var relate_name = "WhoCalledWho";

    var kvp_entity = kg.GetDataModel().GetEntityTypes()
         .First(r => r.Key == entity_name);
    var kvp_relate = kg.GetDataModel().GetRelationshipTypes()
                   .First(r => r.Key == relate_name);

    //Let's delete one field and add a new one from each
    //A field gets deleted implicitly if it is not included in the list of
    //fields - or "properties" in this case....so we will remove the last
    //property from the list
    var entity_props = kvp_entity.Value.GetProperties().Reverse().Skip(1).Reverse();
    var prop_descs = new List<KnowledgeGraphPropertyDescription>();

    foreach (var prop in entity_props)
    {
      if (prop.FieldType == FieldType.Geometry)
      {
        continue;//skip shape
      }
      var prop_desc = new KnowledgeGraphPropertyDescription(prop);
      prop_descs.Add(prop_desc);
    }
    //deal with shape - we need to keep it
    //SchemaBuilder deletes any field not included in the "modify" list
    ShapeDescription shape_desc = null;
    if (kvp_entity.Value.GetIsSpatial())
    {
      var geom_def = kvp_entity.Value.GetShapeDefinition();
      var shape_name = kvp_entity.Value.GetShapeField();
      shape_desc = new ShapeDescription(
        shape_name, geom_def.geometryType, geom_def.sr);
    }
    //add the new entity property
    prop_descs.Add(
      KnowledgeGraphPropertyDescription.CreateStringProperty("foo", 10));
    //make a description for the entity type - ok if shape_desc is null
    var entityDesc = new KnowledgeGraphEntityTypeDescription(
      entity_name, prop_descs, shape_desc);

    //Add the entity type description to the schema builder using 'Modify'
    SchemaBuilder sb = new(kg);
    sb.Modify(entityDesc);

    //Repeat for the relationship - assuming we have at least one custom attribute field
    //that can be deleted on our relationship schema...
    var rel_props = kvp_relate.Value.GetProperties().Reverse().Skip(1).Reverse();
    var rel_prop_descs = new List<KnowledgeGraphPropertyDescription>();

    foreach (var prop in rel_props)
    {
      if (prop.FieldType == FieldType.Geometry)
      {
        continue;//skip shape
      }
      var prop_desc = new KnowledgeGraphPropertyDescription(prop);
      rel_prop_descs.Add(prop_desc);
    }
    //deal with shape - we need to keep it
    //SchemaBuilder deletes any field not included in the "modify" list
    ShapeDescription shape_desc_rel = null;
    if (kvp_relate.Value.GetIsSpatial())
    {
      var geom_def = kvp_relate.Value.GetShapeDefinition();
      var shape_name = kvp_relate.Value.GetShapeField();
      shape_desc_rel = new ShapeDescription(
        shape_name, geom_def.geometryType, geom_def.sr);
    }
    //add a new relationship property
    rel_prop_descs.Add(
      KnowledgeGraphPropertyDescription.CreateStringProperty("bar", 10));
    //make a description for the relationship type - ok if shape_desc is null
    var relDesc = new KnowledgeGraphRelationshipTypeDescription(
      relate_name, rel_prop_descs, shape_desc_rel);

    //Add the relationship type description to the schema builder using 'Modify'
    sb.Modify(relDesc);

    //Run the schema builder
    try
    {
      //Use the KnowledgeGraph extension method 'ApplySchemaEdits(...)'
      //to refresh the Pro UI
      if (!kg.ApplySchemaEdits(sb))
      {
        var err_msg = string.Join(",", sb.ErrorMessages.ToArray());
        System.Diagnostics.Debug.WriteLine($"Entity/Relate Modify error: {err_msg}");
      }
    }
    catch (Exception ex)
    {
      System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
  }
});

Create Attribute Indexes on KG Schemas with SchemaBuilder

await QueuedTask.Run(() =>
{
  using (var kg = GetKnowledgeGraph())
  {
    if (kg == null)
      return;

    var entity_name = "PhoneCall";

    //indexes are managed on the GDB objects...
    var entity_table_def = kg.GetDefinition<TableDefinition>(entity_name);
    var entity_table_desc = new TableDescription(entity_table_def);


    var entity_table_flds = entity_table_def.GetFields();
    AttributeIndexDescription attr_index1 = null;
    AttributeIndexDescription attr_index2 = null;
    foreach (var fld in entity_table_flds)
    {
      //index the first string field
      if (fld.FieldType == FieldType.String && attr_index1 == null)
      {
        if (fld.Name == "ESRI__ID")//special case
          continue;
        //Index _must_ be ascending for KG
        attr_index1 = new AttributeIndexDescription(
          "Index1", entity_table_desc, new List<string> { fld.Name })
        {
          IsAscending = true
        };
      }
      //index the first numeric field (if there is one)
      if ((fld.FieldType == FieldType.BigInteger ||
           fld.FieldType == FieldType.Integer ||
           fld.FieldType == FieldType.Single ||
           fld.FieldType == FieldType.SmallInteger ||
           fld.FieldType == FieldType.Double) && attr_index2 == null)
      {
        attr_index2 = new AttributeIndexDescription(
          "Index2", entity_table_desc, new List<string> { fld.Name })
        {
          IsAscending = true,
          IsUnique = true //optional - unique if all values are to be unique in the index
        };
      }
      if (attr_index1 != null && attr_index2 != null) break;
    }

    if (attr_index1 == null && attr_index2 == null)
      return; //nothing to index

    //Run the schema builder
    try
    {
      SchemaBuilder sb = new(kg);
      if (attr_index1 != null)
        sb.Create(attr_index1);
      if (attr_index2 != null)
        sb.Create(attr_index2);
      if (!kg.ApplySchemaEdits(sb))
      {
        var err_msg = string.Join(",", sb.ErrorMessages.ToArray());
        System.Diagnostics.Debug.WriteLine($"Create index error: {err_msg}");
      }
    }
    catch (Exception ex)
    {
      System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
  }
});

Delete Attribute Indexes on KG Schemas with SchemaBuilder

await QueuedTask.Run(() =>
{
  using (var kg = GetKnowledgeGraph())
  {
    if (kg == null)
      return;

    var entity_name = "PhoneCall";

    //indexes are managed on the GDB objects...
    var entity_table_def = kg.GetDefinition<TableDefinition>(entity_name);
    var entity_table_desc = new TableDescription(entity_table_def);

    var indexes = entity_table_def.GetIndexes();
    foreach (var idx in indexes)
    {
      System.Diagnostics.Debug.WriteLine($"Index {idx.GetName()}");
    }
    var idx1 = indexes.FirstOrDefault(
      idx => idx.GetName().ToLower() == "Index1".ToLower());
    var idx2 = indexes.FirstOrDefault(
      idx => idx.GetName().ToLower() == "Index2".ToLower());

    if (idx1 == null && idx2 == null)
      return;

    //Run the schema builder
    try
    {
      SchemaBuilder sb = new(kg);

      if (idx1 != null)
      {
        var idx_attr = new AttributeIndexDescription(idx1, entity_table_desc);
        sb.Delete(idx_attr);
      }
      if (idx2 != null)
      {
        var idx_attr = new AttributeIndexDescription(idx2, entity_table_desc);
        sb.Delete(idx_attr);
      }

      if (!kg.ApplySchemaEdits(sb))
      {
        var err_msg = string.Join(",", sb.ErrorMessages.ToArray());
        System.Diagnostics.Debug.WriteLine($"Delete index error: {err_msg}");
      }
    }
    catch (Exception ex)
    {
      System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
  }
});

Create Domain and Field Definition on KG Schemas with SchemaBuilder

await QueuedTask.Run(() =>
{
  using (var kg = GetKnowledgeGraph())
  {
    if (kg == null)
      return;

    var entity_name = "Fruit";

    //Domains are managed on the GDB objects...
    var fruit_fc = kg.OpenDataset<FeatureClass>(entity_name);
    var fruit_fc_def = fruit_fc.GetDefinition();

    var fieldFruitTypes = fruit_fc_def.GetFields()
          .FirstOrDefault(f => f.Name == "FruitTypes");
    var fieldShelfLife = fruit_fc_def.GetFields()
        .FirstOrDefault(f => f.Name == "ShelfLife");

    //Create a coded value domain and add it to a new field
    var fruit_cvd_desc = new CodedValueDomainDescription(
      "FruitTypes", FieldType.String, 
      new SortedList<object, string> {
                      { "A", "Apple" },
                      { "B", "Banana" },
                      { "C", "Coconut" }
      })  {
        SplitPolicy = SplitPolicy.Duplicate,
        MergePolicy = MergePolicy.DefaultValue
    };

    //Create a Range Domain and add the domain to a new field description also
    var shelf_life_rd_desc = new RangeDomainDescription(
                                  "ShelfLife", FieldType.Integer, 0, 14);

    var sb = new SchemaBuilder(kg);
    sb.Create(fruit_cvd_desc);
    sb.Create(shelf_life_rd_desc);

    //Create the new field descriptions that will be associated with the
    //"new" FruitTypes coded value domain and the ShelfLife range domain
    var fruit_types_fld = new ArcGIS.Core.Data.DDL.FieldDescription(
                                  "FruitTypes", FieldType.String);
    fruit_types_fld.SetDomainDescription(fruit_cvd_desc);

    //ShelfLife will use the range domain
    var shelf_life_fld = new ArcGIS.Core.Data.DDL.FieldDescription(
  "ShelfLife", FieldType.Integer);
    shelf_life_fld.SetDomainDescription(shelf_life_rd_desc);

    //Add the descriptions to the list of field descriptions for the
    //fruit feature class - Modify schema needs _all_ fields to be included
    //in the schema, not just the new ones to be added.
    var fruit_fc_desc = new FeatureClassDescription(fruit_fc_def);

    var modified_fld_descs = new List<ArcGIS.Core.Data.DDL.FieldDescription>(
      fruit_fc_desc.FieldDescriptions);

    modified_fld_descs.Add(fruit_types_fld);
    modified_fld_descs.Add(shelf_life_fld);

    //Create a feature class description to modify the fruit entity
    //with the new fields and their associated domains
    var updated_fruit_fc =
      new FeatureClassDescription(entity_name, modified_fld_descs,
                                  fruit_fc_desc.ShapeDescription);

    //Add the modified fruit fc desc to the schema builder
    sb.Modify(updated_fruit_fc);

    //Run the schema builder
    try
    {
      if (!kg.ApplySchemaEdits(sb))
      {
        var err_msg = string.Join(",", sb.ErrorMessages.ToArray());
        System.Diagnostics.Debug.WriteLine($"Create domains error: {err_msg}");
      }
    }
    catch (Exception ex)
    {
      System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
  }
});

Delete Domain on KG Schemas with SchemaBuilder

await QueuedTask.Run(() =>
{
  using (var kg = GetKnowledgeGraph())
  {
    if (kg == null)
      return;

    //Get all the domains in the KG
    var domains = kg.GetDomains();
    var sb = new SchemaBuilder(kg);

    foreach (var domain in domains)
    {
      //skip the special provenance domain
      var name = domain.GetName();
      if (string.Compare(name, "esri__provenanceSourceType", true) == 0)
        continue;//skip this one

      //Delete all other domains
      if (domain is RangeDomain rd)
        sb.Delete(new RangeDomainDescription(rd));
      else if (domain is CodedValueDomain cvd)
        sb.Delete(new CodedValueDomainDescription(cvd));
    }

    try
    {
      //note: will throw an InvalidOperationException if there are no operations
      //to run. Will also delete associated fields dependent on deleted domain(s)
      if (!kg.ApplySchemaEdits(sb))
      {
        var err_msg = string.Join(",", sb.ErrorMessages.ToArray());
        System.Diagnostics.Debug.WriteLine($"Delete domains error: {err_msg}");
      }
    }
    catch (Exception ex)
    {
      System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
  }
});
⚠️ **GitHub.com Fallback** ⚠️