0

I am using dotnetrdf to send a Sparql CONSTRUCT query and evaluate the resulting IGraph.

For a start, I am trying to find all Uri nodes that represent individuals of a given type, say,

http://www.example.com/InterestingThing

So, I would like to use the GetTriplesWithPredicateObject method, passing http://www.w3.org/1999/02/22-rdf-syntax-ns#type as a predicate and http://www.example.com/InterestingThing as an object. That method expects two objects implementing the INode interface, so I am trying to find the respective nodes with the graph's GetUriNode method.

Problem: The predicate is not found.

When looking at the IGraph object in the debugger, it seems that GetUriNode can find only what is contained in the Nodes enumeration of the graph - and that contains only Uri nodes that have been used as subjects or objects, but none that are used as predicates.

Now, the predicate Uri nodes are evidently there - I can see them when exploring the Triples object of the graph. They appear in the Triple instances that can be retrieved directly from that object, and they can also be found in the PredicateNodes property of the same object.

Interestingly, some querying methods of IGraph such as GetTriplesWithObject or GetTriplesWithPredicate do have overloads that accept a mere Uri instead of an INode - but GetTriplesWithPredicateObject does not.

Now, various possible workarounds are obvious:

  • Use the overload of GetTriplesWithPredicate that takes only a Uri instance, then filter the resulting triples manually.
  • Manually search through all triples to get the desired triples.
  • Manually search through the list of PredicateNodes and find the ones with the searched for Uris.

All of these seem unnecessarily cumbersome, though - as the existing methods suggest at first glance that one should simply be able to use GetUriNode to retrieve two nodes and then pass those to one of the ready-made querying methods such as GetTriplesWithObjectPredicate.

Therefore, my question is:

Am I missing anything, either in the dotnetrdf library or conceptually, in RDF, or is this behavior with the obstacles it poses to developers by design?

Here is a C# test case to reproduce the problem (querying DBpedia):

using System;
using System.Collections.Generic;
using System.Linq;

using VDS.RDF;
using VDS.RDF.Query;

namespace Test
{
    class Program
    {
        public static void Main(string[] args)
        {
            // Querying
            // All entries to analyze are declared to belong to a temporary type, and
            // two properties are mapped to temporary property identifiers.

            Console.WriteLine();

            var endpoint = new SparqlRemoteEndpoint(new Uri("http://dbpedia.org/sparql"));

            string query = "PREFIX dbpedia-owl: <http://dbpedia.org/ontology/>\n"
                + "PREFIX ex: <http://www.example.com/>\n"
                + "\n"
                + "CONSTRUCT {\n"
                + "  ?r a ex:record.\n"
                + "  ?r ex:prop1 ?v1.\n"
                + "  ?r ex:prop2 ?v2.\n"
                + "}\n"
                + "WHERE {\n"
                + "  {\n"
                + "    SELECT ?r\n"
                + "    WHERE {\n"
                + "      ?r a dbpedia-owl:Work.\n"
                + "      FILTER(EXISTS { ?r dbpedia-owl:originalLanguage [] } && EXISTS { ?r dbpedia-owl:author/dbpedia-owl:birthPlace [] }).\n"
                + "    } LIMIT 5\n"
                + "  }\n"
                + "  ?r dbpedia-owl:originalLanguage ?v1.\n"
                + "  ?r dbpedia-owl:author/dbpedia-owl:birthPlace ?v2.\n"
                + "}";

            Console.WriteLine(query);
            Console.WriteLine();

            IGraph graph = endpoint.QueryWithResultGraph(query);

            PrintNodes("IGraph.Nodes", graph.Nodes);

            Console.WriteLine("All triples from IGraph.Triples:");
            foreach (var triple in graph.Triples) {
                Console.WriteLine("- S: " + NodeToString(triple.Subject));
                Console.WriteLine("  P: " + NodeToString(triple.Predicate));
                Console.WriteLine("  O: " + NodeToString(triple.Object));
            }
            Console.WriteLine();

            PrintNodes("IGraph.Triples.SubjectNodes", graph.Triples.SubjectNodes);
            PrintNodes("IGraph.Triples.PredicateNodes", graph.Triples.PredicateNodes);
            PrintNodes("IGraph.Triples.ObjectNodes", graph.Triples.ObjectNodes);

            // Graph Analysis
            // The following code tries to retrieve exactly the items of the temporary
            // type "record".

            var typeNode = TryFindUriNode(graph, new Uri(NamespaceMapper.RDF + "type"));
            var recordNode = TryFindUriNode(graph, new Uri("http://www.example.com/record"));

            Console.WriteLine();
            TryFindAllNodes("subjects", graph, graph.Triples.SubjectNodes.OfType<IUriNode>().Select(node => node.Uri));
            TryFindAllNodes("predicates", graph, graph.Triples.PredicateNodes.OfType<IUriNode>().Select(node => node.Uri));
            TryFindAllNodes("objects", graph, graph.Triples.ObjectNodes.OfType<IUriNode>().Select(node => node.Uri));
            Console.WriteLine();

            var createdTypeNode = graph.CreateUriNode(new Uri(NamespaceMapper.RDF + "type"));
            var createdRecordNode = graph.CreateUriNode(new Uri("http://www.example.com/record"));

            if ((typeNode != null) && (recordNode != null)) {
                Console.WriteLine("{0} triple(s) with found predicate and found object.",
                                  graph.GetTriplesWithPredicateObject(typeNode, recordNode).Count());
            }
            if (typeNode != null) {
                Console.WriteLine("{0} triple(s) with found predicate and created object.",
                                  graph.GetTriplesWithPredicateObject(typeNode, createdRecordNode).Count());
            }
            if (recordNode != null) {
                Console.WriteLine("{0} triple(s) with created predicate and found object.",
                                  graph.GetTriplesWithPredicateObject(createdTypeNode, recordNode).Count());
            }
            Console.WriteLine("{0} triple(s) with created predicate and created object.",
                              graph.GetTriplesWithPredicateObject(createdTypeNode, createdRecordNode).Count());
        }

        private static string NodeToString(INode node)
        {
            return string.Format("{0} ({1})", node, node.GetType().Name);
        }

        private static void PrintNodes(string title, IEnumerable<INode> nodes)
        {
            Console.WriteLine(title + ":");
            foreach (var node in nodes) {
                Console.WriteLine("- " + NodeToString(node));
            }
            Console.WriteLine();
        }

        private static INode TryFindUriNode(IGraph graph, Uri uri)
        {
            var result = graph.GetUriNode(uri);
            if (result == null) {
                Console.WriteLine(uri.ToString() + " was NOT found by IGraph.GetUriNode.");
            } else {
                Console.WriteLine(uri.ToString() + " WAS found by IGraph.GetUriNode.");
            }
            return result;
        }

        private static void TryFindAllNodes(string title, IGraph graph, IEnumerable<Uri> uris)
        {
            Console.WriteLine("Trying to find all " + title + ":");
            foreach (Uri uri in uris) {
                TryFindUriNode(graph, uri);
            }
        }
    }
}

This is the output of the sample program:

PREFIX dbpedia-owl: <http://dbpedia.org/ontology/>
PREFIX ex: <http://www.example.com/>

CONSTRUCT {
  ?r a ex:record.
  ?r ex:prop1 ?v1.
  ?r ex:prop2 ?v2.
}
WHERE {
  {
    SELECT ?r
    WHERE {
      ?r a dbpedia-owl:Work.
      FILTER(EXISTS { ?r dbpedia-owl:originalLanguage [] } && EXISTS { ?r dbpedia-owl:author/dbpedia-owl:birthPlace [] }).
    } LIMIT 5
  }
  ?r dbpedia-owl:originalLanguage ?v1.
  ?r dbpedia-owl:author/dbpedia-owl:birthPlace ?v2.
}

IGraph.Nodes:
- http://dbpedia.org/resource/Electra_(Sophocles) (UriNode)
- http://dbpedia.org/resource/Peer_Gynt (UriNode)
- http://dbpedia.org/resource/Pictures_from_the_Insects'_Life (UriNode)
- http://dbpedia.org/resource/The_Field (UriNode)
- http://dbpedia.org/resource/The_Lady_from_the_Sea (UriNode)
- http://www.example.com/record (UriNode)
- http://dbpedia.org/resource/Ancient_Greek (UriNode)
- http://dbpedia.org/resource/Colonus (UriNode)
- http://dbpedia.org/resource/Norwegian_language (UriNode)
- http://dbpedia.org/resource/Skien (UriNode)
- http://dbpedia.org/resource/Norway (UriNode)
- http://dbpedia.org/resource/Czech_language (UriNode)
- http://dbpedia.org/resource/Kingdom_of_Bohemia (UriNode)
- http://dbpedia.org/resource/Bohemia (UriNode)
- http://dbpedia.org/resource/Mal%C3%A9_Svato%C5%88ovice (UriNode)
- http://dbpedia.org/resource/Austria-Hungary (UriNode)
- http://dbpedia.org/resource/English_language (UriNode)
- http://dbpedia.org/resource/Irish_Free_State (UriNode)
- http://dbpedia.org/resource/Listowel (UriNode)
- http://dbpedia.org/resource/County_Kerry (UriNode)

All triples from IGraph.Triples:
- S: http://dbpedia.org/resource/Electra_(Sophocles) (UriNode)
  P: http://www.w3.org/1999/02/22-rdf-syntax-ns#type (UriNode)
  O: http://www.example.com/record (UriNode)
- S: http://dbpedia.org/resource/Electra_(Sophocles) (UriNode)
  P: http://www.example.com/prop1 (UriNode)
  O: http://dbpedia.org/resource/Ancient_Greek (UriNode)
- S: http://dbpedia.org/resource/Electra_(Sophocles) (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Colonus (UriNode)
- S: http://dbpedia.org/resource/Peer_Gynt (UriNode)
  P: http://www.w3.org/1999/02/22-rdf-syntax-ns#type (UriNode)
  O: http://www.example.com/record (UriNode)
- S: http://dbpedia.org/resource/Peer_Gynt (UriNode)
  P: http://www.example.com/prop1 (UriNode)
  O: http://dbpedia.org/resource/Norwegian_language (UriNode)
- S: http://dbpedia.org/resource/Peer_Gynt (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Skien (UriNode)
- S: http://dbpedia.org/resource/Peer_Gynt (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Norway (UriNode)
- S: http://dbpedia.org/resource/Pictures_from_the_Insects'_Life (UriNode)
  P: http://www.w3.org/1999/02/22-rdf-syntax-ns#type (UriNode)
  O: http://www.example.com/record (UriNode)
- S: http://dbpedia.org/resource/Pictures_from_the_Insects'_Life (UriNode)
  P: http://www.example.com/prop1 (UriNode)
  O: http://dbpedia.org/resource/Czech_language (UriNode)
- S: http://dbpedia.org/resource/Pictures_from_the_Insects'_Life (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Kingdom_of_Bohemia (UriNode)
- S: http://dbpedia.org/resource/Pictures_from_the_Insects'_Life (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Bohemia (UriNode)
- S: http://dbpedia.org/resource/Pictures_from_the_Insects'_Life (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Mal%C3%A9_Svato%C5%88ovice (UriNode)
- S: http://dbpedia.org/resource/Pictures_from_the_Insects'_Life (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Austria-Hungary (UriNode)
- S: http://dbpedia.org/resource/The_Field (UriNode)
  P: http://www.w3.org/1999/02/22-rdf-syntax-ns#type (UriNode)
  O: http://www.example.com/record (UriNode)
- S: http://dbpedia.org/resource/The_Field (UriNode)
  P: http://www.example.com/prop1 (UriNode)
  O: http://dbpedia.org/resource/English_language (UriNode)
- S: http://dbpedia.org/resource/The_Field (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Irish_Free_State (UriNode)
- S: http://dbpedia.org/resource/The_Field (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Listowel (UriNode)
- S: http://dbpedia.org/resource/The_Field (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/County_Kerry (UriNode)
- S: http://dbpedia.org/resource/The_Lady_from_the_Sea (UriNode)
  P: http://www.w3.org/1999/02/22-rdf-syntax-ns#type (UriNode)
  O: http://www.example.com/record (UriNode)
- S: http://dbpedia.org/resource/The_Lady_from_the_Sea (UriNode)
  P: http://www.example.com/prop1 (UriNode)
  O: http://dbpedia.org/resource/Norwegian_language (UriNode)
- S: http://dbpedia.org/resource/The_Lady_from_the_Sea (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Skien (UriNode)
- S: http://dbpedia.org/resource/The_Lady_from_the_Sea (UriNode)
  P: http://www.example.com/prop2 (UriNode)
  O: http://dbpedia.org/resource/Norway (UriNode)

IGraph.Triples.SubjectNodes:
- http://dbpedia.org/resource/Electra_(Sophocles) (UriNode)
- http://dbpedia.org/resource/Peer_Gynt (UriNode)
- http://dbpedia.org/resource/Pictures_from_the_Insects'_Life (UriNode)
- http://dbpedia.org/resource/The_Field (UriNode)
- http://dbpedia.org/resource/The_Lady_from_the_Sea (UriNode)

IGraph.Triples.PredicateNodes:
- http://www.w3.org/1999/02/22-rdf-syntax-ns#type (UriNode)
- http://www.example.com/prop1 (UriNode)
- http://www.example.com/prop2 (UriNode)

IGraph.Triples.ObjectNodes:
- http://www.example.com/record (UriNode)
- http://dbpedia.org/resource/Ancient_Greek (UriNode)
- http://dbpedia.org/resource/Colonus (UriNode)
- http://dbpedia.org/resource/Norwegian_language (UriNode)
- http://dbpedia.org/resource/Skien (UriNode)
- http://dbpedia.org/resource/Norway (UriNode)
- http://dbpedia.org/resource/Czech_language (UriNode)
- http://dbpedia.org/resource/Kingdom_of_Bohemia (UriNode)
- http://dbpedia.org/resource/Bohemia (UriNode)
- http://dbpedia.org/resource/Mal%C3%A9_Svato%C5%88ovice (UriNode)
- http://dbpedia.org/resource/Austria-Hungary (UriNode)
- http://dbpedia.org/resource/English_language (UriNode)
- http://dbpedia.org/resource/Irish_Free_State (UriNode)
- http://dbpedia.org/resource/Listowel (UriNode)
- http://dbpedia.org/resource/County_Kerry (UriNode)

Trying to find all subjects:
http://dbpedia.org/resource/Electra_(Sophocles) WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Peer_Gynt WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Pictures_from_the_Insects'_Life WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/The_Field WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/The_Lady_from_the_Sea WAS found by IGraph.GetUriNode.
Trying to find all predicates:
http://www.w3.org/1999/02/22-rdf-syntax-ns#type was NOT found by IGraph.GetUriNode.
http://www.example.com/prop1 was NOT found by IGraph.GetUriNode.
http://www.example.com/prop2 was NOT found by IGraph.GetUriNode.
Trying to find all objects:
http://www.example.com/record WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Ancient_Greek WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Colonus WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Norwegian_language WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Skien WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Norway WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Czech_language WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Kingdom_of_Bohemia WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Bohemia WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Mal‚_Svatonovice WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Austria-Hungary WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/English_language WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Irish_Free_State WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/Listowel WAS found by IGraph.GetUriNode.
http://dbpedia.org/resource/County_Kerry WAS found by IGraph.GetUriNode.

http://www.w3.org/1999/02/22-rdf-syntax-ns#type was NOT found by IGraph.GetUriNode.
http://www.example.com/record WAS found by IGraph.GetUriNode.
5 triple(s) with created predicate and found object.
5 triple(s) with created predicate and created object.

As can be seen, the IGraph.Nodes enumeration does not include the predicate Uris such as http://www.example.com/prop1 or http://www.w3.org/1999/02/22-rdf-syntax-ns#type. Likewise, the output shows that predicates are not found by GetUriNode, while subjects and objects are.

O. R. Mapper
  • 20,083
  • 9
  • 69
  • 114
  • Thank you for linking to the documentation! It makes it much easier to get a feel for what certain things _should_ do. However, could you show the actual code that you've used, as well as some of the data that you're working with? It's very hard to come up with an answer without seeing the exact problem. – Joshua Taylor Jul 31 '13 at 10:37
  • @JoshuaTaylor: I'll try, but it will take a while - my current code is in the middle of a bigger project, but I'll try and create an independent sample. – O. R. Mapper Jul 31 '13 at 10:38
  • 1
    Can you just use [CreateURINode](http://www.dotnetrdf.org/api/dotNetRDF~VDS.RDF.INodeFactory.CreateUriNode.html) to _create_ the `rdf:type` node? This is similar to the approach I'd take in Jena, where I'd use something like `ResourceFactory.createProperty( http://...ns#type )`. (Actually, Jena has a constant `RDF.type` for convenience that I'd use, but this is the approach I'd use for one that wasn't defined as a constant ahead of time.) – Joshua Taylor Jul 31 '13 at 10:41
  • @JoshuaTaylor: Interesting; I didn't notice that possibility because it wasn't in the [`IGraph` docs](http://www.dotnetrdf.org/api/dotNetRDF~VDS.RDF.IGraph.CreateUriNode~Overloads.html) (just in those of the `INodeFactory` superinterface). This seems to work for the predicate - but when always staying on the safe side by using just `CreateURINode` instead of `GetUriNode` also for objects, this fails, as no triples are found any more. I'm preparing an example source code to illustrate this. – O. R. Mapper Jul 31 '13 at 10:58
  • Oh, and one more thing for now: I am querying DBpedia, in case that has any bearing on the returned graphs. – O. R. Mapper Jul 31 '13 at 11:03
  • Hm, I'd expect the graph to be able to find matching triples, regardless of who created the `INode` for the predicate and object, so that behavior is surprising. Maybe the minimal code example will reveal something… – Joshua Taylor Jul 31 '13 at 11:36
  • @JoshuaTaylor: For some reason, I cannot reproduce the latter problem now any more, not even in the original code - maybe I got no results due to a temporary DBpedia glitch that coincided with my testing the changed code? If this works, always using `CreateUriNode` instead of `GetUriNode` would seem like a viable alternative. Anyway, I have added a small example program that does what I am trying to do and shows in its output how predicates are omitted from `IGraph.Nodes` and are not found by using `GetUriNode`. – O. R. Mapper Jul 31 '13 at 11:54
  • @JoshuaTaylor: I've accepted your answer as it is a viable way to deal with this issue, though I'd still like to know the rationale behind this - as it basically renders `GetUriNode` partially useless. I have updated the sample application to explicitly check which of the nodes mentioned in triples are found by `GetUriNode` - also for other future readers who may have some helpful hints. Thanks for pointing out `CreateUriNode(Uri)`, in any case :-) – O. R. Mapper Jul 31 '13 at 12:09
  • 1
    DBpedia can be a bit flaky at times (there are other questions/answers on SO that have examples) and also has [rate limiting](http://stackoverflow.com/a/17266895/1281433). Sometimes the results are unpredictable. – Joshua Taylor Jul 31 '13 at 12:10

2 Answers2

3

You can use CreateURINode from INodeFactory (which IGraph extends) to create the rdf:type node, and have something like:

graph.GetTriplesWithPredicateObject(
  graph.CreateUriNode( UriFactory.createUri( "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" )), ,
  graph.CreateUriNode( UriFactory.createUri( "http://www.example.com/InterestingThing" ))
)

This is similar to the approach I'd take in Jena, where I'd use something like ResourceFactory.createProperty( http://...ns#type ). (Actually, Jena has a constant RDF.type for convenience that I'd use, but this is the approach I'd use for one that wasn't defined as a constant ahead of time.)

Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • 1
    There is a `RdfSpecsHelper.RdfType` constant though it is a String so you still have to convert to a `Uri` or the code will try and interpret it as a prefixed named – RobV Jul 31 '13 at 17:10
2

This behaviour is entirely by design, something that appears only in the predicate position is considered an edge in the graph rather than a node in the graph.

As the Working with Graphs documentation states the following:

To select Nodes there are methods which can be used to find a Node from a Graph (if it exists) which are the GetXNode() methods where X is the type of the node to be retrieved. Note that this method only returns a value if the given value exists as a node in the Graph i.e. it occurs in the Subject/Object position of a triple in this graph.

Note: If you just want to get a Node instance for other usages regardless of whether it already exists in the Graph you should use the CreateXNode() methods instead.

Maybe a GetEdge() and corresponding Edges property would be useful?

RobV
  • 28,022
  • 11
  • 77
  • 119
  • Not sure about the `GetEdge`/`Edges` suggestion. At least from an API-usability point of view ... what I want to get is called "node", as I have to supply an object that implements `INode`, and that object is actually already present in the graph as an instance of class `UriNode`, and I can already find it in a property called `PredicateNodes`. I'm not sure it would be useful to think whether the `INode` I'm trying to find is available by calling `GetUriNode` or `GetEdge`. At least for the use case described in my question, something like `GetGraphElementNode` (searching in subjects, ... – O. R. Mapper Jul 31 '13 at 17:35
  • ... predicates and objects) would be more useful. (i.e. in this case, `GraphElement` would clarify that the term "node" is used with respect to object types in that method name, as opposed to "node" and "edge" that denote the respective parts of an RDF graph.) That said, the method `GetEdge` and a corresponding `Edges` property would certainly make sense with respect to symmetry towards the node-related methods/property. – O. R. Mapper Jul 31 '13 at 17:37
  • So perhaps `GetVertex()` replaces the current behavior, `GetEdge()` specifically for prefixes and `GetNode()` changes to get a node if it is either an edge/vertex? – RobV Jul 31 '13 at 19:22
  • Though I should note that ultimately the intention is to remove `GetNode()` methods entirely in favor of `CreateNode()`. This is because `GetNode()` starts to make little sense when nodes are no longer scoped to specific graphs which is a significant behavioral and design change that will eventually be in 2.0 – RobV Jul 31 '13 at 19:26
  • `GetVertex` and `GetEdge` vs. `GetNode` sounds like a good naming choice, too - despite the backwards compatibility issues when changing the meaning of `GetNode` at this point. If `GetNode` is discontinued in favor of `CreateNode`, won't `CreateNode` create any clutter in the `Graph` instance when trying to retrieve nodes for Uris that are not actually in the graph? I mean, is there no reference to the newly created node in the `Nodes` list, for example? I'm looking forward to seeing the updates, in any case - great work with the library :) – O. R. Mapper Jul 31 '13 at 20:27