Skip to content

ProConcepts Knowledge Graph

UmaHarano edited this page Nov 6, 2023 · 1 revision

This ProConcepts covers the KnowledgeGraph API. Knowledge graphs, also known as graph networks, consist of nodes (or "entities") linked between them with relationships. Knowledge graphs support both relational GDB (sql) queries and openCypher graph network queries.

ArcGIS.Core.dll ArcGIS.Desktop.Mapping.dll

Language:      C#
Subject:       Knowledge Graph
Contributor:   ArcGIS Pro SDK Team <arcgisprosdk@esri.com>
Organization:  Esri, http://www.esri.com
Date:          10/02/2023
ArcGIS Pro:    3.2
Visual Studio: 2022

In this topic

In this topic

Background

A knowledge graph, also known as a semantic network or graph network, is a representation of a network consisting of nodes (i.e. "entities") and the links or relationships between them (ideally in a "human-understandable" form). Knowledge graphs are usually implemented to bring together diverse sets of information, much of it non-spatial, to help users extract knowledge from the knowledge graph data. Knowledge graphs are also very efficient at discovering related content. As they store relationships together with the entity data, knowledge graphs can follow the path of a given relationship very quickly - much quicker than relational systems where the relationships have to be derived (usually from shared primary/foreign key values). Knowledge graphs can also acquire and integrate adjacent information through additional relationships to derive new knowledge (for the user).

Knowledge graphs are stored in graph databases (like Neo4j) and visualized as a graph structure, prompting the term knowledge “graph.” They can be made up of datasets from many different sources and each can differ in structure. The knowledge graph data model defines the types of entities and relationships that can exist in the knowledge graph along with their properties. Entities in the model represent real-world objects, concepts, or events such as a harbor, a test plan, and/or a scheduled maintenance activity or repair. Relationships in the model express how entities are associated with each other as well as the correct context. Having the correct context, for example, allows the knowledge graph to determine the difference between, say, "Apple", the brand, and "apple", the fruit. A knowledge graph allows you to discover how parts of the system are connected, which factors in the system have the biggest impact, and which hidden connections have more influence than expected. Entities with a spatial location can be connected with other entities that do not. Additionally, a special type of entity, called "Document", can be added to an ArcGIS knowledge graph. "Documents" can provide additional context for an entity (or a relationship in which it participates) as well as provide authoritative sources for the facts stored in the properties of entities and relationships. Documents can be pictures, presentations, text or Adobe Acrobat PDF files, websites, and so on.

In the ArcGIS Platform, knowledge graphs are stored in a "Graph Store". A graph store is the database that the portal's ArcGIS Knowledge Server uses to store the entities and relationships that compose the knowledge graph. Knowledge graphs can be stored as hosted knowledge graphs that are entirely managed by ArcGIS Knowledge in an Enterprise portal. Alternatively, knowledge graphs can be stored in a NoSQL user-managed data store in Neo4j. A user-managed data store must be registered with a portal's ArcGIS Knowledge Server site. There will be one registered NoSQL data store for each knowledge graph (requires ArcGIS Pro 3.0 or later). As content is added to the knowledge graph, users can explore the relationships between entities in the investigation, document their findings, create maps and link charts, and use different forms of analysis to improve their understanding of the system. Consult Get started with ArcGIS Knowledge in the ArcGIS Pro documentation for more information.

An ArcGIS Knowledge graph also supports both a (traditional) relational (gdb) model and a graph model for its entities, relationships, and documents. Within the relational gdb model, entities and relationships with a spatial location are represented as features in feature classes (and feature layers) and entities and relationships with no spatial location are represented as rows in tables. Knowledge graph feature classes and tables support all the standard geodatabase functionality, such as search, select, and edit sessions.

Overview

At Pro 3.2, the first release of the Knowledge Graph public api is available and provides the following capabilities:

  • Access to the KnowledgeGraph gdb data store and (relational) gdb data model (i.e. access to entity and relationship feature classes, tables, and definitions) and support for relational queries.
  • KnowledgeGraph queries using openCypher query language to find subsets of the entities, relationships, documents, and provenance it contains and identify how different entities are connected.
  • KnowledgeGraph text searches against indexed entity and relationship types.
  • Access to the KnowledgeGraph data model and metadata to describe the different entity and relationship types, their associated properties and provenance (provenance describes the origin of the information used to create entities and relationships).
  • Adding Knowledge graphs to a map or scene to create a knowledge graph layer. A knowledge graph layer is a composite layer (i.e. "group layer") with a knowledge graph as its data source. The knowledge graph layer contains feature layers and (standalone) tables. The feature layers provide access to entity and relationship types which have a spatial location whereas the standalone tables provide access to entity and relationship types that do not.

Editing capabilities for the knowledge graph graph data model; access to Investigation and Link Chart views, contents, and associated context; access to associated knowledge graph items and project items in the Pro Catalog view are not supported via the api at 3.2 and will be included in future releases.

KnowledgeGraph Datastore

The KnowledgeGraph datastore and associated core data model objects can be found in the ArcGIS.Core.Data.Knowledge namespace within the ArcGIS.Core assembly. Accessing a KnowledgeGraph datastore follows the same pattern as other supported relational data stores, namely:

  • Creation of a KnowledgeGraph datastore connection using a connection property object - KnowledgeGraphConnectionProperties. The KnowledgeGraphConnectionProperties should point to the URI of the KnowledgeGraph service being connected to or "opened".
  • Access and Retrieval of all contained datasets (there is one dataset per spatial and non-spatial entity and relationship type). Use the generic public T OpenDataset<T>(string name) method on the (knowledge graph) data store where "T" is either a FeatureClass or Table*.
  • Access and Retrieval of dataset definitions via the generic public IReadOnlyList<T> GetDefinition<T>() and public IReadOnlyList<T> GetDefinitions<T>() data store methods where "T" can be either of FeatureClassDefinition or TableDefinition*.

KnowledgeGraph datastores also have a SpatialReference accessed via a public SpatialReference GetSpatialReference() method. All feature classes within a knowledge graph must share a common coordinate system.

*Currently only feature classes, feature class definitions, tables, and table definitions are supported in a knowledge graph

The following snippet shows creating a connection to a KnowledgeGraph datastore and retrieving all of its datasets and dataset definitions:

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

var kg_props = 
  new KnowledgeGraphConnectionProperties(new Uri(URL));
using(var kg = new KnowledgeGraph(kg_props)) //connect
{
  var fc_names = new List<string>();
  var tbl_names = new List<string>();
  int c = 0;

  //get "relational" definitions and datasets - only feature classes, tables,
  //and associated definitions are supported
  System.Diagnostics.Debug.WriteLine("\r\nFeatureClassDefinitions:");

  var fc_defs = kg.GetDefinitions<FeatureClassDefinition>();
  foreach (var fc_def in fc_defs)
  {
    System.Diagnostics.Debug.WriteLine(
     $"  FeatureClassDefinition[{c++}] {fc_def.GetName()}, {fc_def.GetAliasName()}");
    fc_names.Add(fc_def.GetName());
  }

  System.Diagnostics.Debug.WriteLine("\r\nFeature classes:");

  c = 0;
  foreach (var fc_name in fc_names)
  {
    using (var fc = kg.OpenDataset<FeatureClass>(fc_name))
     System.Diagnostics.Debug.WriteLine(
      $"  FeatureClass[{c++}] {fc.GetName()}");
  }

  System.Diagnostics.Debug.WriteLine("\r\nTableDefinitions:");

  c = 0;
  var tbl_defs = kg.GetDefinitions<TableDefinition>();
  foreach (var tbl_def in tbl_defs)
  {
    System.Diagnostics.Debug.WriteLine(
      $"  TableDefinitions[{c++}] {tbl_def.GetName()}, {tbl_def.GetAliasName()}");
    tbl_names.Add(tbl_def.GetName());
  }

  System.Diagnostics.Debug.WriteLine("\r\nTables:");

  c = 0;
  foreach (var tbl_name in tbl_names)
  {
    using(var tbl = kg.OpenDataset<Table>(tbl_name))
     System.Diagnostics.Debug.WriteLine(
       $"  Table[{c++}] {tbl.GetName()}");
  }
}

KnowledgeGraph Layer

A knowledge graph layer is a composite layer with a knowledge graph as its data source. Knowledge graphs can be added to either a map or a scene. The knowledge graph layer contains one knowledge graph feature layer per entity and relationship type with a spatial location and one knowledge graph standalone table for each entity and relationship type that does not. All feature layers in the knowledge graph layer share the same spatial reference. The component layers and standalone tables can be manipulated via the UI and API in much the same way as any other feature layer or standalone table. Their behavior and appearance can be controlled by modifying their properties and symbology respectively. Knowledge graph feature layer and standalone table CIM definitions can be retrieved via the standard "get" and "set" definition calls on the feature layer or standalone table instance same as any other and are of type CIMFeatureLayer and CIMStandaloneTable respectively.

The following code examples show various ways to create a knowledge graph layer:

string URL = @"https://acme.com/server/rest/services/Hosted/Acme_KG/KnowledgeGraphServer";
var map = MapView.Active.Map;

//Use a kg URI with "CreateLayer"
QueuedTask.Run(() => { 
    var kg_layer = LayerFactory.Instance.CreateLayer(new Uri(URL), map) 
                                                        as KnowledgeGraphLayer;

//Use a LayerCreationParams (or KnowledgeGraphLayercreationParams)
QueuedTask.Run(() => { 
  var lyr_params = new LayerCreationParams(new Uri(URL)) {
     Name = "Acme KnowledgeGraph",
     MapMemberPosition = MapMemberPosition.AddToBottom,
     IsVisible = false
  };
  var kg_layer = LayerFactory.Instance.CreateLayer<KnowledgeGraphLayer>(lyr_params, map);

//Use a KnowledgeGraphLayercreationParams with a KnowledgeGraph
QueuedTask.Run(() => { 
  var kg_props = new KnowledgeGraphConnectionProperties(new Uri(URL));
  using(var kg = new KnowledgeGraph(kg_props)) {
     var kg_params = new KnowledgeGraphLayercreationParams(kg) {
        Name = "Acme KnowledgeGraph"
     }
     var kg_layer = LayerFactory.Instance.CreateLayer<KnowledgeGraphLayer>(kg_params, map);

To retrieve the currently connected KnowledgeGraph datastore from the knowledge graph (composite) layer use any of its children (whether feature layer or standalone table) (recall: the KnowledgeGraphLayer itself is just the container for the knowledge graph feature layers and tables):

 KnowledgeGraph kg = null;
 var map = MapView.Active.Map;

 var kg_layer = map.GetLayersAsFlattenedList()
                      .OfType<ArcGIS.Desktop.Mapping.KnowledgeGraphLayer>()
                      .FirstOrDefault();
 if (kg_layer  != null)
   return;//There is no KnowledgeGraph layer

 QueuedTask.Run(() => {
   //Use a child feature layer to get to the KG datastore
   var fl = kg_layer.GetLayersAsFlattenedList().OfType<FeatureLayer>()?.FirstOrDefault();
   if (fl != null) 
      kg = fl.GetFeatureClass().GetDatastore() as KnowledgeGraph;

   //Note: StandaloneTable is ok too
   //var tbl = kg_layer.GetStandaloneTablesAsFlattenedList().FirstOrDefault();
   //if (tbl != null) 
   // kg = tbl.GetTable().GetDatastore() as KnowledgeGraph;

When dealing with the KnowledgeGraphLayer and its component feature layers and/or standalone tables, access and query use the familiar geodatabase relational model: features and rows related together by shared "key" values. This is illustrated in the following example which performs a relational search on the enitity type "Utility" extracted from the KnowledgeGraph. Note the use of standard gdb search constructs QueryFilter, RowCursor, Row, and rowValue = row[FieldName]:

//perform a standard relational query against an entity type from a 
//KnowledgeGraph datastore...

 using(var kg = new KnowledgeGraph(
                     new KnowledgeGraphConnectionProperties(new Uri(kg_service_uri)))) {
   //Get a particular feature class by name:
   var utility_fc = kg.OpenDataset<FeatureClass>("Utility");
   var number_of_utilities = utility_fc.GetCount();
   //Query for a particular utility...
   var qf = new QueryFilter() {
     WhereClause = "Utility_Name = 'Acme Electric Utility Co.',
     SubFields = "*"
   };
   //Do the search
   using(var rc = utility_fc.Search(qf)) {
      if (rc.MoveNext()) {
        var utility_address = rc.Current["Address"];
        //etc, etc.
      }
   }
 }
   

KnowledgeGraph Graph Data Model

A KnowledgeGraph datastore also provides access to the components of the graph data model (in addition to the afore mentioned gdb relational model components). Graph data model components are retrieved via openCypher graph query and (text) search results. The KnowledgeGraph datastore provides a KnowledgeGraphDataModel that can be traversed for the metadata that describes the individual knowledge graph "graph" entity and relationship types within the graph data model as well as their provenance (if any). The graph data model is detailed in the following section.

KnowledgeGraph Graph Data Model Types

The knowledge graph data model defines the types of entities and relationships that exist in the knowledge graph and their properties. Entities typically represent people, places, and things, and relationships define how the entities are associated or "connected". Entities and relationships can be spatial or non-spatial. Their properties define/describe their characteristics. A special type of entity, called a Document can be added to any ArcGIS knowledge graph to provide additional context for an entity or a relationship (in which it participates). Documents can be pictures, presentations, text or Adobe Acrobat PDF files, website links, and so on. Knowledge graph can also contain provenance. Provenance can be added to a knowledge graph to describe entity and relationship "origins" or "lineage". Provenance can describe which organizations, people, sensors, etc. may have been involved with an associated entity and/or their relationship information (i.e. provenance is a "chain of custody" of sorts). Provenance can be stored as entities, relationships and/or as entity and relationship properties.

The different types of entities present within a knowledge graph are each represented by an entity type. An entity type defines a homogeneous collection of entities with a common set of properties and a spatial feature type (if the given entity type is spatially enabled). A relationship type performs a similar role for relationships. Each relationship type defines a homogenous collection of relationships that can exist between two entity types, with a common set of properties and a spatial feature type (if the given relationship type is spatially enabled). A property, similar to a gdb field, has a name and value type. Properties each have a role, identified by the public KnowledgeGraphPropertyRole GetRole() method. KnowledgeGraphPropertyRole can be: a "regular" role meaning the property is an attribute or "characteristic" of an entity or relationship; the role can be a document property role (self-explanatory); or a role indicating the property is associated with provenance.

The KnowledgeGraphDataModel and primary associated classes (and enums) are diagrammed and described below:

kg_data_model

  • KnowledgeGraphDataModel represents the data model for the knowledge graph, including but not limited to entity and relationship types, spatial reference, and information about generation of unique identifiers. Access via a knowledgeGraph.GetDataModel() method call.
  • KnowledgeGraphNamedObjectType is the abstract base class for all entity and relationship types. Named object types contain metadata about the entity and relationship types in the knowledge graph including their role and properties.
  • KnowledgeGraphEntityType derives from KnowledgeGraphNamedObjectType. There is one KnowledgeGraphEntityType per individual entity type (or "category") in the graph. All entities stored in the knowledge graph must belong to a KnowledgeGraphEntityType. Entity instances will each have a type name value that matches the entity type name of which they are a part*
  • KnowledgeGraphRelationshipType derives from KnowledgeGraphNamedObjectType. There is one KnowledgeGraphRelationshipType per individual relationship "type" in the graph. All relationships stored in the knowledge graph must belong to a KnowledgeGraphRelationshipType. Relationship instances will each have a type name value that matches the relate type name of which they are a part. Relationship types also store a collection of end points represented as a KnowledgeGraphEndPoint. An end point stores the type names of the origin and destination entity types that participate in the relationship.
  • KnowledgeGraphProperty describes a property or "attribute" of an entity or a relationship. Properties are stored in the knowledge graph as key/value pairs (similar to a .NET dictionary) where the key is a unique (string) name within the set of properties for the given type. Property descriptions are retrieved off either an entity or relationship type (via the abstract base class KnowledgeGraphNamedObjectType "GetProperties()" method). The key of a given property value within an entity or relationship will match the name of its corresponding KnowledgeGraphProperty "type" (retrieved from the relevant KnowledgeGraphEntityType or KnowledgeGraphRelationshipType). Properties also have a role, identified by the KnowledgeGraphPropertyRole enum accessed via kg_prop.GetRole(). The role can be Regular, Document, or Provenance.
  • KnowledgeGraphEndPoint is associated with KnowledgeGraphRelationshipType. KnowledgeGraphRelationshipTypes have a collection of end points from which the entity type names for the origin and destination entity types participating in a given relationship can be retrieved.

*Knowledge graphs can contain two special "entity" types:

  • An entity type with the role KnowledgeGraphNamedObjectTypeRole.Document. There can only be one document entity type per knowledge graph - meaning all documents stored in a knowledge graph will be entities of that same entity type. The name of the document entity type is the name of "the" KnowledgeGraphEntityType with the document role. There is always a Document entity type defined in a knowledge graph unless it uses a NoSQL data store with user-managed data in Neo4j.
  • An entity type with the role KnowledgeGraphNamedObjectTypeRole.Provenance. The provenance type is stored in the knowledge graph meta entity type collection which is accessed via kg_dm.GetMetaEntityTypes() (note the "meta" in the method name) and not via kg_dm.GetEntityTypes(). Provenance is the only entity type that is currently stored in the knowledge graph meta entity type collection. Provenance represents origin and/or chain of custody information related to graph entities and relationships. Unlike Documents, the ability to capture provenance within a knowledge graph is not enabled by default.

Additional knowledge graph data model descriptions can be found in the Essential ArcGIS Knowledge vocabulary. The following examples show how to get the names of the Document and Provenance entity types, if present:

 protected string GetDocumentEntityTypeName(KnowledgeGraphDataModel kg_dm)  {
   var entity_types = kg_dm.GetEntityTypes();
   foreach (var entity_type in entity_types){
     if entity_type.Value.GetRole() == KnowledgeGraphNamedObjectTypeRole.Document)
        return entity_type.Value.GetName();
   }
   return "";//prob a Neo4j user managed KG
 }

 protected 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 "";//Not all knowledge graphs have Provenance
 }

The KnowledgeGraphDataModel also contains meta data describing how unique identifiers in the knowledge graph are generated (not shown in the above class diagram). Identifier metadata can be accessed off the KnowledgeGraphDataModel via the public KnowledgeGraphIdentifierInfo GetIdentifierInfo() method. KnowledgeGraphIdentifierInfo is the abstract base class for knowledge graph identifier information. There are two concrete classes that derive from KnowledgeGraphIdentifierInfo: KnowledgeGraphNativeIdentifier meaning that the knowledge graph is using a database native identifier as the unique identifier for entities and relationships, or a KnowledgeGraphUniformIdentifier meaning that the knowledge graph is using a specific property as the unique identifier for entities and relationships.

The following example illustrates how to retrieve data model information from the knowledge graph:

//Utility method showing how to access named object type metadata
private void ProcessKGNamedObjectType(
   int level, KnowledgeGraphNamedObjectType kg_no_type,
   string prefix) {
   var spaces = new string(' ', level);
   var indent = $"{spaces}{prefix}";

   var end_points = new List<KnowledgeGraphEndPoint>()
       as IReadOnlyList<KnowledgeGraphEndPoint>;

   switch (kg_no_type)
   {
      case KnowledgeGraphEntityType kg_e_type:
         break;
      case KnowledgeGraphRelationshipType kg_r_type:
         end_points = kg_r_type.GetEndPoints();
         break;
   }

   System.Diagnostics.Debug.WriteLine($"{indent} Name: '{kg_no_type.GetName()}'");
   System.Diagnostics.Debug.WriteLine($"{indent} Role: {kg_no_type.GetRole()}");
   System.Diagnostics.Debug.WriteLine($"{indent} AliasName: '{kg_no_type.GetAliasName()}'");
   System.Diagnostics.Debug.WriteLine($"{indent} HasObjectID: {kg_no_type.GetHasObjectID()}");
   System.Diagnostics.Debug.WriteLine(
     $"{indent} ObjectIDPropertyName: {kg_no_type.GetObjectIDPropertyName()}");
   System.Diagnostics.Debug.WriteLine($"{indent} IsStrict: {kg_no_type.GetIsStrict()}");

   System.Diagnostics.Debug.WriteLine($"{indent} Properties:\r\n{indent} ---------------");
   var kg_props = kg_no_type.GetProperties();
   var prop = 0;
   foreach (var kg_prop in kg_props) {
      System.Diagnostics.Debug.WriteLine($"{indent} Property[{prop}]:");
      System.Diagnostics.Debug.WriteLine(
                $"{indent}   DefaultVisibility: {kg_prop.GetHasDefaultVisibility()}");
      System.Diagnostics.Debug.WriteLine(
                $"{indent}   IsSystemMaintained: {kg_prop.GetIsSystemMaintained()}");
      System.Diagnostics.Debug.WriteLine(
                $"{indent}   Role: {kg_prop.GetRole()}");
      prop++;
   }

   if (end_points.Count == 0)
      return;

   System.Diagnostics.Debug.WriteLine($"{indent} EndPoints:");

   foreach (var end_point in end_points) {
      System.Diagnostics.Debug.WriteLine(
          $"{indent}   " +
          $"OriginEntityTypeName: '{end_point.GetOriginEntityTypeName()}', " +
          $"DestinationEntityTypeName: '{end_point.GetDestinationEntityTypeName()}'");
   }
}

//Utility method showing how to access knowledge graph identifier info
private void ProcessKGIdentifierInfo(KnowledgeGraphIdentifierInfo kg_id_info) {
   var kg_id_gen = kg_id_info.GetIdentifierGeneration();
   if (kg_id_info is KnowledgeGraphNativeIdentifier kg_ni) {
     System.Diagnostics.Debug.WriteLine($"IdentifierInfo is KnowledgeGraphNativeIdentifier");
   }
   else if (kg_id_info is KnowledgeGraphUniformIdentifier kg_ui) {
     System.Diagnostics.Debug.WriteLine($"IdentifierInfo is KnowledgeGraphUniformIdentifier");
     System.Diagnostics.Debug.WriteLine($"IdentifierName {kg_ui.GetIdentifierName()}");
   }
   System.Diagnostics.Debug.WriteLine($"Identifier MethodHint {kg_id_gen.GetMethodHint()}");
}
...

//elsewhere - retrieve the KG graph data model from the KnowledgeGraph via "GetDataModel()"
using(var kg = new KnowledgeGraph(
                     new KnowledgeGraphConnectionProperties(new Uri(kg_service_uri)))) {

    var kg_name = System.IO.Path.GetFileName(
                     System.IO.Path.GetDirectoryName(kg_service_uri));

   //Get the graph data model
   var kg_dm = kg.GetDataModel();

   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()}");
 
   //Write out KG identifier info
   var kg_id_info = kg_dm.GetIdentifierInfo();
   System.Diagnostics.Debug.WriteLine("");
   ProcessKGIdentifierInfo(kg_id_info);

   //Write out KG Meta Entity Type info - i.e. Provenance, if there is any
   System.Diagnostics.Debug.WriteLine("\r\n MetaEntityTypes:\r\n ------------");

   var dict_types = kg_dm.GetMetaEntityTypes();
   var key_count = 0;
   foreach(var kvp in dict_types)
   {
     System.Diagnostics.Debug.WriteLine($"\r\n MetaEntity ({key_count++}): '{kvp.Key}'");
     ProcessKGNamedObjectType(1, kvp.Value, "  ");
   }

   //Write out KG Entity Type info (includes Document entity type)
   System.Diagnostics.Debug.WriteLine("\r\n EntityTypes:\r\n ------------");

   dict_types = kg_dm.GetEntityTypes();
   key_count = 0;
   foreach (var kvp in dict_types)
   {
     System.Diagnostics.Debug.WriteLine($"\r\n Entity ({key_count++}): '{kvp.Key}'");
     ProcessKGNamedObjectType(1, kvp.Value, "  ");
   }

   //Write out KG Relationship Type info
   System.Diagnostics.Debug.WriteLine("\r\n RelationshipTypes:\r\n ------------");

   var dict_rel_types = kg_dm.GetRelationshipTypes();
   key_count = 0;
   foreach (var kvp in dict_rel_types)
   {
     System.Diagnostics.Debug.WriteLine($"\r\n Relationship ({key_count++}): '{kvp.Key}'");
     ProcessKGNamedObjectType(1, kvp.Value, "  ");
   }
}

KnowledgeGraph Graph Queries and Text Search

The KnowledgeGraph api supports both graph query and text searches. Graph queries via the api are based on the openCypher declarative query language. Text searches via the api use the Apache Lucene - Query Parser syntax. Graph queries and text searches are asynchronous and both use a similar pattern derived from the RealtimeCursor, via KnowledgeGraphCursor to retrieve query or text search results. Similar to real-time/stream layer queries, when a graph query or text search has been specified, callers wait or "a-wait" for rows to be returned (asynchronously) from the server. Returned rows can contain either primitives (such as ints, strings, doubles, etc.) or knowledge graph values of type KnowledgeGraphValue. Graph queries, text searches, and the KnowledgeGraphValue data model are explained in the following sections.

KnowledgeGraph SubmitQuery

To submit a graph query, addins instantiate a knowledge graph query filter of type KnowledgeGraphQueryFilter with a kg_query_filter.QueryText using an openCypher formatted query string. openCypher borrows quite a lot of syntax from SQL and, similar to SQL, is built using clauses. Clauses can use keywords like WHERE and ORDER BY, familiar to users of SQL, and a construct, not found in SQL, called MATCH. MATCH clauses specify which entities, relationships, and properties are to be searched for in the query - (similar to a SQL SELECT). The Neo4j primer on their Cypher query language is also an excellent resource for the syntax and usage of graph queries (note: ArcGIS Knowledge graph queries can only retrieve values. Graph query clauses that can update values are not supported.)

ArcGIS has extended the cypher language with its own spatial operators that can be added to graph query expressions. ArcGIS Knowledge supports the following custom spatial operators for use in graph queries:

  • ST_Equals—Returns entities with equal geometries. The syntax is esri.graph.ST_Equals(geometry1, geometry2).
  • ST_Intersects—Returns entities with intersecting geometries. The syntax is esri.graph.ST_Intersects(geometry1, geometry2).
  • ST_Contains—Returns entities whose geometries are contained by the specified geometry. The syntax is esri.graph.ST_Contains(geometry1, geometry2).

ArcGIS Knowledge also provides a datetime(DATE-TIME-VALUE-HERE) method that can be combined into a knowledge graph graph query to convert dates and times to coordinated universal time (UTC). Within an ArcGIS knowledge graph date-time values must always be expressed in coordinated universal time (UTC). As the knowledge graph does not convert dates and times within queries to UTC automatically, the datetime() utility should be used to do the conversion. More information on the datetime() utility and spatial operators can be found in ArcGIS Enterprise Query a knowledge graph. More general information on cypher support for date and time types can be found on the Cypher Property Graph Query Language github here

Addins can also specify a kg_query_filter.ProvenanceBehavior to determine whether or not provenance entities should be included in the query results if the knowledge graph contains provenance information. To include provenance in the results, specify kg_query_filter.ProvenanceBehavior = KnowledgeGraphProvenanceBehavior.Include. The default is "Exclude". Specifying KnowledgeGraphProvenanceBehavior.Include for a query against a knowledge graph that has no provenance will result in an empty result set (i.e. graph_row_cursor.MoveNext() will return false). To check for provenance, use the following routine:

protected string GetProvenanceEntityTypeName(KnowledgeGraphDataModel kg_dm) {
   //Provenance is stored as a meta entity type....use "GetMetaEntityTypes()" to check
   //for provenance...
   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 "";
 }

 protected bool SupportsProvenance(KnowledgeGraph kg) {
   //if there is a provenance entity type then the KnowledgeGraph
   //supports provenance
   return !string.IsNullOrEmpty(GetProvenanceEntityTypeName(kg.GetDataModel()));
 }

 //elsewhere...usage...
 private bool _includeProvenance = ...;

 var kg_qf = new KnowledgeGraphQueryFilter() {
   QueryText = query,
 };
 //do we have provenance that can be included?
 if (_includeProvenance && SupportsProvenance(kg)) {
    //Only use "Include" if the Knowledge graph _has_ provenance
    kg_qf.ProvenanceBehavior = KnowledgeGraphProvenanceBehavior.Include;
 }

An output spatial reference for all returned geometries can be specified via KnowledgeGraphQueryFilter.OutputSpatialReference. However, as of 3.2, this parameter is currently ignored. It is added for use in a future release. At 3.2, geometry values will always be returned in the spatial reference of the underlying knowledge graph and must be projected to a different spatial reference by the addin code (regardless of the OutputSpatialReference value).

The following example illustrates the basic pattern for executing a knowledge graph query. Provenance is specified as being included in the returned values if the knowledge graph (being queried) has provenance:

 //Define a query - select the first 10 entities
 var kg_qf = new KnowledgeGraphQueryFilter() {
   QueryText = "MATCH (n) RETURN n LIMIT 10",
   ProvenanceBehavior = SupportsProvenance(kg) ? 
                   KnowledgeGraphProvenanceBehavior.Include :
                   KnowledgeGraphProvenanceBehavior.Exclude
 };

 //Submit the graph query
 using (var kg_rc = kg.SubmitQuery(kg_qf)) {

   //Do a non-blocking await waiting for rows to be retrieved
   while (await kg_rc.WaitForRowsAsync()) {
     //Rows are available
     while (kg_rc.MoveNext())  {
         //process each row
         using (var graph_row = kg_rc.Current)  {
           int val_count = (int)graph_row.GetCount();
           for (int i = 0; i < val_count; i++) {
              var row_val = graph_row[i];
              //TODO - process value
              //...
           }
        }
     }
   }
 }

KnowledgeGraph SubmitSearch

Text searches can be submitted against a knowledge graph using a KnowledgeGraphSearchFilter. Addins specify the kg_search_filter.SearchText using the Apache Lucene - Query Parser syntax. The simplest type of query consists of a single term or phrase like "book", "Car", or "cats and dogs". More complex search strings can be constructed using boolean operators, fields, wildcards, and so forth. Consult the Apache Lucene - Query Parser syntax reference for more information. A kg_search_filter.SearchTarget of type KnowledgeGraphNamedTypeCategory should be specified to control what will be the target of the search. Entities, relationships, and provenance can all be specified. The default search target will be KnowledgeGraphNamedTypeCategory.Entity if a SearchTarget is not specified. A limit on the number of returned rows can be specified via kg_search_filter.MaxRowCount. The default is 100. Use kg_search_filter.Offset to skip rows (in the results). The Offset sets the index of the first result to be returned.

In this example, a text search is implemented looking for all entities with a name or property value of "Acme Electric Co.". Notice that, with the exception of calling "SubmitSearch" (rather than "SubmitQuery"), the workflow for processing a text search is the same as the workflow for processing a graph query:

 var kg_sf = new KnowledgeGraphSearchFilter() {
    SearchTarget = KnowledgeGraphNamedTypeCategory.Entity,
    SearchText = "Acme Electric Co.",
    ReturnSearchContext = true,
    MaxRowCount = 10
 };

 //Submit the text search
 using (var kg_rc = kg.SubmitSearch(kg_sf)) {
    //Same workflow for a search as w/ processing a query...
    while (await kg_rc.WaitForRowsAsync()) {
      //Rows are available
      while (kg_rc.MoveNext())  {
         //process each row
         using (var graph_row = kg_rc.Current)  {
           int val_count = (int)graph_row.GetCount();
           for (int i = 0; i < val_count; i++) {
              var row_val = graph_row[i];
              //TODO - process value
              //...
           }
        }
      }
    }
 }

KnowledgeGraphCursor WaitForRowsAsync

Once a query or text search has been submitted, callers perform a non-blocking wait, or "a-wait" via kg_row_cursor.WaitForRowsAsync(). WaitForRowsAsync has a built-in timeout of (approximately) 30 seconds - this is the maximum amount of time it will wait on an open connection to receive a batch of rows from the server and is not configurable. Once a batch of rows is retrieved (within the built-in timeout duration), the WaitForRowsAsync Task completes and clients can call kg_row_cursor.MoveNext() to retrieve and process the returned rows (same as with a relational gdb query). Once the rows have been processed, the caller can make another call to WaitForRowsAsync to retrieve the next batch. Each call to WaitForRowsAsync will reset the 30 second timeout and callers can do a non-blocking a-wait for the next batch of rows to be retrieved. If the full 30 second elapses before any rows are retrieved on the open connection, the connection is closed and no further rows can be retrieved (for "that" particular search or query).

KnowledgeGraphCursor WaitForRowsAsync Cancellation

Even though the default timeout duration is not configurable, clients can build-in their own timeout using the overload of WaitForRowsAsync that takes a CancellationToken constructed with a user defined timeout. When the timeout specified for the CancellationToken has been reached, the current WaitForRowsAsync Task is cancelled and completes immediately and a TaskCanceledException is thrown from WaitForRowsAsync to indicate that the awaited WaitForRowsAsync Task was cancelled (due to the timeout). Note: The cancelled Task will complete regardless of whether the underlying streaming connection is still retrieving rows. A user defined timeout defined within a CancellationToken can not be reset. The only way to reset the timeout is to construct a new CancellationToken.

In the following example, an addin is specifying a user defined timeout of 20 seconds. If the total row retrieval and processing time exceeds 20 seconds, the CancellationToken will timeout and throw a TaskCanceledException halting the row retrieval regardless of whether the client is still processing rows or not.

 //a knowledge graph cursor has been returned from a SubmitQuery or SubmitSearch...

 //auto-cancel after 20 seconds
 var cancel = new CancellationTokenSource(new TimeSpan(0, 0, 20));

 //wrap the WaitForRowsAsync call with a try/catch for a
 //TaskCanceledException
 try {
   //wait for rows. Monitor for TaskCanceledException
   //successive calls to WaitForRowsAsync will _not_ reset a cancellation
   //token timeout if one was specified...
   while (await rc.WaitForRowsAsync(cancel.Token))
   {
     //process retrieved rows...
     while (rc.MoveNext())
     {
       
     }
   }
 }
 catch(TaskCanceledException tce)
 {
   //TODO - we were cancelled! A TaskCanceledException 
   //will be thrown if row retrieval and processing exceeds
   //the specified user-defined timeout (if there was one)
 }
 finally
 {
   //Clean up, etc.
 }

Additional information on WaitForRowsAsync and cancellation can be found in the ProConcepts StreamLayers document.

KnowledgeGraph Graph Query and Text Search Results

Results returned from graph queries and text searches are in the form of "graph values" and primitives. The graph values and primitives are retrieved from the KnowledgeGraphCursor's current graph row. The current knowledge graph row is of type KnowledgeGraphRow and is available from the kg_cursor.Current property (same as with rows and features when using a gdb relational row cursor). Values retrieved from a knowledge graph row can include entities, relationships, documents, and entity provenance as well as primitive values (such as ints, doubles, strings, geometries, etc.).

Graph values all derive from the abstract base class ArcGIS.Core.Data.Knowledge.KnowledgeGraphValue. Graph values can contain other graph values - usually if a particular query or search results in an array of values or a path being returned (a path is a sequence of alternating entities and relationships). As graph values can contain other graph values, they must be recursively processed to ensure that all values returned from a graph query or text search are fully traversed. This is different from processing rows and features from a relational gdb query which are never recursive.

In addition, the KnowledgeGraphRow is an array which must be enumerated to retrieve all contained values (primitive or graph). The number of values in the knowledge graph row array will be dependent on the number of values that were specified in the "RETURN" clause of the query (text search results typically return single value rows). For example, a query of the form "MATCH ...... RETURN v1, v2, ...., vn" would return row arrays containing "v(n)" values - one per value in the return clause. The number of values in the knowledge graph row array can be retrieved via its kg_row.GetCount() method. What types of values a given KnowledgeGraphRow array may contain depends on the specifics of the graph query RETURN clause or values returned from a text search (e.g. did the graph query specify an array or path be returned). openCypher queries have a huge amount of flexibility that they can use to specify both what values and how those values are to be returned. For example, assume we have a simple query MATCH (p:Person) RETURN p, p.name - this would return two values - the entity value "p" and the primitive value from the property "p.name" (presumably a string). kg_row.GetCount() in this case would return "2". RETURN statements can also specify values be returned as lists - for example, as a list of literals: RETURN [1,2,3,4,5]; or as the result of evaluating a query expression: MATCH (p:Person) RETURN [p, p.name] (note the use of "[]" square brackets)- in both these cases, the KnowledgeGraphRow array will contain a single value (kg_row.GetCount() == 1) of type KnowledgeGraphArrayValue - which is, itself, another array. Knowledge graph array values, same as the KnowledgeGraphRow array, can contain both knowledge graph values and primitives mixed together depending on, once again, the nature of the given query.

Graph queries can also return "paths". Graph queries that take the general form of MATCH (e1)-[]->(e2) describe a relationship, or "path", between two or more entities (in this case, a "directed" relationship or path between "e1" and "e2" - note the "->" pointing to e2). The series of connected nodes and relationships that result from this type of query form the path. Paths are returned as a KnowledgeGraphPathValue. KnowledgeGraphPathValues can contain a collection of entities and relationships (depending on how the path was specified in the graph query). Both collections in the path value must be enumerated to evaluate all returned knowledge graph values.

An example of the general pattern for processing the values from the KnowledgeGraphRow array is shown below. Note the use of GetCount() and the KnowledgeGraphRow indexer to retrieve all values from the returned row array (the custom "ProcessKnowledgeGraphRowValue" method is provided further below in this section):

 //
 //WaitForRowsAsync Task has completed with a value of true...
 //assume we are processing retrieved rows from a search or query
 while (kg_rc.MoveNext())  { //same as with a GDB relational cursor
     //process each KnowledgeGraphRow - available in the "Current" property
     using (var graph_row = kg_rc.Current)  {
        //Get the count of returned values in the row array
        int val_count = (int)graph_row.GetCount();
        for (int i = 0; i < val_count; i++) {
          //get each value
          var row_val = graph_row[i];
          //recursively process...
          ProcessKnowledgeGraphRowValue(0, row_val);//can be a KG Value or primitive
        }
     }
 }

The complete KnowledgeGraphValue data model in the ArcGIS.Core.Data.Knowledge namespace is shown below:

kg_graph_value

  • KnowledgeGraphValue is the abstract base class for all derived knowledge graph values. Examine public KnowledgeGraphValueType KnowledgeGraphValueType to determine the value type and/or check using a cast (to the derived type).
  • KnowledgeGraphPrimitiveValue can wrap any (supported) primitive value - text, numeric, data/time, guid, geometry, blob. The primitive value itself can be retrieved via the public object GetValue() method. Note: queries and searches return primitive values directly (as ints, longs, strings, doubles, etc.). They do not use KnowledgeGraphPrimitiveValue. KnowledgeGraphPrimitiveValue is for future use in the api.
  • KnowledgeGraphPathValue represents a series of connected entities and relationships usually described by an openCypher query of the form (e1)-[]->(e2), (e1)-[]->(e2)<-[]->(e3) and so on. Entities and relationships connected by a path can be retrieved via the public KnowledgeGraphEntityValue GetEntity(ulong index) and public KnowledgeGraphRelationshipValue GetRelationship(ulong index) methods respectively.
  • KnowledgeGraphArrayValue can contain an array of 0 or more KnowledgeGraphValue values and/or primitive values depending on the nature of the query.
  • KnowledgeGraphObjectValue is the base class for any object value in the knowledge graph. An object value stores values as properties (not unlike the fields of a row or feature). Each property consists of a key|value pair. The value can be any KnowledgeGraphValue or primitive. The list of property keys can be retrieved via the public IReadOnlyList<string> GetKeys() method. Use the keys with the object value indexer public object this[string key] to retrieve the corresponding property value. Queries typically return named objects like entities and relationships (derived from KnowledgeGraphObjectValue), however, KnowledgeGraphObjectValue instances themselves can be returned in queries when the query defines an anonymous type "on-the-fly". For example, the query string MATCH (b:Beer) RETURN { Xbeer: { Xname: b.name, Xid: b.id } } defines an anonymous type "XBeer" (based on the entity "Beer") which would be returned as a KnowledgeGraphObjectValue (and not as an entity).
  • KnowledgeGraphNamedObjectValue is the base class for named object values and derives from KnowledgeGraphObjectValue. Named objects include entities and relationships. In addition to being able to store property values, named objects (can) also have an id (usually a guid) that uniquely identifies them, an object id, and a "type". The "type" describes the type or category for the particular named object value and corresponds to their associated underlying geodatabase table name and corresponding KnowledgeGraphNamedObjectType contained in the KnowledgeGraphDataModel (refer to the data model) section)
  • KnowledgeGraphEntityValue derives from KnowledgeGraphNamedObjectValue. All entities, therefore, have values stored as properties, a unique id, can have an object id, and have a (string) type. Entities also have an arbitrary string label retrieved via public string GetLabel(). Entities that have a geometry can be accessed as features from a feature class whose name is the same as the *KnowledgeGraphNamedObjectValue type string. Entities that do not have a geometry can be accessed as rows from a table (whose name is the same as the *KnowledgeGraphNamedObjectValue type string).
  • KnowledgeGraphRelationshipValue derives from KnowledgeGraphNamedObjectValue, same as entities, meaning that relationships in a graph model can also have properties, a unique id, an object id, and have a (string) type. Relationships too are accessible as features or rows from the knowledge graph datastore depending on whether they have a geometry or not. Relationships contain an origin and destination id identifying the origin and destination entities associated with the relationship. The ids can be retrieved via the public object GetOriginID() and public virtual object GetDestinationID() methods respectively.

The following example shows recursively processing the knowledge graph values retrieved from a KnowledgeGraph graph query and/or text search:

#region KG Utilities

protected string GetDocumentEntityTypeName(KnowledgeGraphDataModel kg_dm)  {
   var entity_types = kg_dm.GetEntityTypes();
   foreach (var entity_type in entity_types){
     if entity_type.Value.GetRole() == KnowledgeGraphNamedObjectTypeRole.Document)
        return entity_type.Value.GetName();
   }
   return "";//prob a Neo4j user managed KG
 }

 protected 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 "";//Not all knowledge graphs have Provenance
 }

 protected bool SupportsProvenance(KnowledgeGraph kg) {
   //if there is a provenance entity type then the KnowledgeGraph
   //supports provenance
   return !string.IsNullOrEmpty(GetProvenanceEntityTypeName(kg.GetDataModel()));
}

 protected bool GetEntityIsProvenance(KnowledgeGraphEntityValue entity, 
                                               string provenanceName = "") {
   if (string.IsNullOrEmpty(provenanceName))
      return false;
   return entity.GetTypeName() == provenanceName;
 }

#endregion KG Utilities

#region Read KG Values
//All entities and relationships, including Documents and Provenance
private void PrintGraphNamedObjectValue(int level,
   KnowledgeGraphNamedObjectValue kg_named_obj_val) {
   var spaces = new string(' ', level);
   var indent = $"{spaces}";

   if (kg_named_obj_val is KnowledgeGraphEntityValue kg_entity) {
      var is_doc = false;
      var is_provenance = false;
      if (!string.IsNullOrEmpty(_kg_DocName)) {
         is_doc = GetEntityIsDocument(kg_entity, _kg_DocName);
      }
      if (!string.IsNullOrEmpty(_kg_ProvenanceName)){
         is_provenance = GetEntityIsProvenance(kg_entity, _kg_ProvenanceName);
      }
      System.Diagnostics.Debug.WriteLine($"{indent} IsDocument: {is_doc}");
      System.Diagnostics.Debug.WriteLine($"{indent} IsProvenance: {is_provenance}");

      var label = kg_entity.GetLabel();
      System.Diagnostics.Debug.WriteLine($"{indent} Label: '{label}'");
    }
    else if (kg_named_obj_val is KnowledgeGraphRelationshipValue kg_rel) {
      var has_entity_ids = kg_rel.GetHasRelatedEntityIDs();
      System.Diagnostics.Debug.WriteLine($"{indent} HasRelatedEntityIDs: {has_entity_ids}");
      if (has_entity_ids) {
         var origin_id = kg_rel.GetOriginID();
         var dest_id = kg_rel.GetDestinationID();
         System.Diagnostics.Debug.WriteLine($"{indent} OriginID: {origin_id}");
         System.Diagnostics.Debug.WriteLine($"{indent} DestinationID: {dest_id}");
      }
    }
    var id = kg_named_obj_val.GetID();
    var oid = kg_named_obj_val.GetObjectID();
    var type_name = kg_named_obj_val.GetTypeName();

    System.Diagnostics.Debug.WriteLine($"{indent} ID: {id}");
    System.Diagnostics.Debug.WriteLine($"{indent} ObjectID: {oid}");
    System.Diagnostics.Debug.WriteLine($"{indent} TypeName: '{type_name}'");
 }

 //Base class for named objects (e.g. entities and relationships) _and_ 
 //anonymous objects
 private void ProcessGraphObjectValue(
   int level, KnowledgeGraphObjectValue kg_obj_val, string prefix = "") {

   var spaces = new string(' ', level);
   var indent = $"{spaces}{prefix}";

   switch (kg_obj_val) {
      case KnowledgeGraphEntityValue kg_entity:
         PrintGraphNamedObjectValue(level + 1, kg_entity);
         break;
      case KnowledgeGraphRelationshipValue kg_rel:
         PrintGraphNamedObjectValue(level + 1, kg_rel);
         break;
      default:
         break;
   }

   var keys = kg_obj_val.GetKeys();

   var key_names = new List<string>();
   foreach (var key in keys)
        key_names.Add($"'{key}'");
   var key_string = string.Join(',', key_names.ToArray());
   System.Diagnostics.Debug.WriteLine($"{indent} Keys: {key_string}");

   var count = 0;
   foreach (var key2 in keys) {
       var key_val = kg_obj_val[key2];
       //Recurse to process property key values
       ProcessKnowledgeGraphRowValue(level + 1, key_val, $"({count++})['{key2}'] = ");
   }
 }

 //All graph value types
 private void ProcessGraphValue(
   int level, KnowledgeGraphValue kg_val, string prefix = "") {

   var spaces = new string(' ', level);
   var indent = $"{spaces}{prefix}";

   switch (kg_val) {
      case KnowledgeGraphPrimitiveValue kg_prim:
         //We should not get a KG Primitive Value from a Query or Search
         System.Diagnostics.Debug.WriteLine($"{indent} Primitive:");
         var val = kg_prim.GetValue();
         ProcessKnowledgeGraphRowValue(level + 1, val, " ");//Recurse
         return;
      case KnowledgeGraphArrayValue kg_array:
         System.Diagnostics.Debug.WriteLine($"{indent} Array:");
         var count = (int)kg_array.GetSize();
         for (int i = 0; i < count; i++) {
            var array_val = kg_array[(ulong)i];
            ProcessKnowledgeGraphRowValue(level + 1, array_val, $"[{i}] = ");//Recurse
         }
         System.Diagnostics.Debug.WriteLine("");
         return;
      case KnowledgeGraphPathValue kg_path:
         System.Diagnostics.Debug.WriteLine($"{indent} Path:");
         //Entities:
         var entity_count = (long)kg_path.GetEntityCount();
         System.Diagnostics.Debug.WriteLine($"{indent} Entities - count: {entity_count}:");
         for (long i = 0; i < entity_count; i++) {
            ProcessGraphObjectValue(
                  level + 2, kg_path.GetEntity((ulong)i), $" e[{i}]:");//Recurse
         }

         //Relationships
         var relate_count = (long)kg_path.GetRelationshipCount();
         System.Diagnostics.Debug.WriteLine($"\r\n{indent} Relationships - count: {relate_count}:");
         for (long i = 0; i < relate_count; i++)
         {
            ProcessGraphObjectValue(
                  level + 2, kg_path.GetRelationship((ulong)i), $" r[{i}]:");//Recurse
         }
         return;
      case KnowledgeGraphObjectValue kg_object:
         //Anonymous
         ProcessGraphObjectValue(level, kg_object, " object:");//Recurse
         return;
      default:
         //Should never get here
         var type_string = kg_val.GetType().ToString();
         System.Diagnostics.Debug.WriteLine($"{indent} Unknown: '{type_string}'");
         return;
   }
 }

 //Process all primitives and graph values
 private void ProcessKnowledgeGraphRowValue(
   int level, object value, string prefix = ""){

   var spaces = new string(' ', level + 1);
   var indent = $"{spaces}{prefix}";

   if (null == value) {
       System.Diagnostics.Debug.WriteLine($"{indent} null");
       return;
   }

   switch (value) {
      //Graph values
      case KnowledgeGraphValue kg_val:
         var kg_type = kg_val.KnowledgeGraphValueType.ToString();
         System.Diagnostics.Debug.WriteLine($"{indent} KnowledgeGraphValue: '{kg_type}'");
         ProcessGraphValue(level + 1, kg_val, " ");//Recurse
         return;
      //Primitives
      case System.DBNull dbn:
         System.Diagnostics.Debug.WriteLine($"{indent} DBNull.Value");
         return;
      case string str:
         System.Diagnostics.Debug.WriteLine($"{indent} '{str}' (string)");
         return;
      case long l_val:
         System.Diagnostics.Debug.WriteLine($"{indent} {l_val} (long)");
         return;
      case int i_val:
         System.Diagnostics.Debug.WriteLine($"{indent} {i_val} (int)");
         return;
      case short s_val:
         System.Diagnostics.Debug.WriteLine($"{indent} {s_val} (short)");
         return;
      case double d_val:
         System.Diagnostics.Debug.WriteLine($"{indent} {d_val} (double)");
         return;
      case float f_val:
         System.Diagnostics.Debug.WriteLine($"{indent} {f_val} (float)");
         return;
      case DateTime dt_val:
         System.Diagnostics.Debug.WriteLine($"{indent} {dt_val} (DateTime)");
         return;
      case DateOnly dt_only_val:
         System.Diagnostics.Debug.WriteLine($"{indent} {dt_only_val} (DateOnly)");
         return;
      case TimeOnly tm_only_val:
         System.Diagnostics.Debug.WriteLine($"{indent} {tm_only_val} (TimeOnly)");
         return;
      case DateTimeOffset dt_tm_offset_val:
         System.Diagnostics.Debug.WriteLine($"{indent} {dt_tm_offset_val} (DateTimeOffset)");
         return;
      case System.Guid guid_val:
         var guid_string = guid_val.ToString("B");
         System.Diagnostics.Debug.WriteLine($"{indent} '{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(
            $"{indent} geometry: {geom_type}, empty: {is_empty}, sr_wkid {wkid} (shape)");
         return;
      default:
         //Blob?, others?
         var type_str = value.GetType().ToString();
         System.Diagnostics.Debug.WriteLine($"{indent} Primitive: {type_str}");
         return;
   }
 }

#endregion Read KG Values

 //elsewhere - submit the query
 KnowledgeGraph kg = .... ;

 var kg_qf = new KnowledgeGraphQueryFilter() {
   //Return the first 10 (arbitrary) entities
   QueryText = "MATCH (n) RETURN n LIMIT 10",
   ProvenanceBehavior = SupportsProvenance(kg) ? 
             KnowledgeGraphProvenanceBehavior.Include :
             KnowledgeGraphProvenanceBehavior.Exclude
 };

 //Submit the graph query
 using (var kg_rc = kg.SubmitQuery(kg_qf)) {//Same workflow for SubmitSearch

   //Do a non-blocking await waiting for rows to be retrieved
   while (await kg_rc.WaitForRowsAsync()) {

     //Rows are available
     while (kg_rc.MoveNext())  {
         //process each row
         using (var graph_row = kg_rc.Current) {
           int val_count = (int)graph_row.GetCount();
           for (int i = 0; i < val_count; i++) {
              var row_val = graph_row[i];
              //recursively process...
              ProcessKnowledgeGraphRowValue(0, row_val);//can be a KG Value or primitive
           }
        }
     }//while
   }//while
 }

Additional Reading

Developing with ArcGIS Pro

    Migration


Framework

    Add-ins

    Configurations

    Customization

    Styling


Arcade


Content


CoreHost


DataReviewer


Editing


Geodatabase

    3D Analyst Data

    Plugin Datasources

    Topology

    Object Model Diagram


Geometry

    Relational Operations


Geoprocessing


Knowledge Graph


Layouts

    Reports


Map Authoring

    3D Analyst

    CIM

    Graphics

    Scene

    Stream

    Voxel


Map Exploration

    Map Tools


Networks

    Network Diagrams


Parcel Fabric


Raster


Sharing


Tasks


Workflow Manager Classic


Workflow Manager


Reference

Clone this wiki locally