RDF - Object Mapping for RDF in .net

MakoLab open-source component

Language integrated queries

Language Integrated Query, or LINQ, has become a de-facto standard for accessing various data sources in .NET. By combining concepts of functional programming and a simple yet powerful syntax inspired by SQL LINQ allows programmers to query data in a very expressive manner.

Hence we decided to attempt to implement a LINQ provider for Romantic Web. Each query in Romantic Web is transformed to a SPARQL query and executed against the underlying triple store.

Below examples show how to begin querying RDF with LINQ and what the queries look like. All examples assume that the meta graph URI is urn:meta:graph.

It is important to note that this is still a work in progress. The shape of the SPARQL queries could change, shall we discover a more efficient way to transform LINQ. Also because implementing a LINQ provider is a fairly complex task, the full list of available functionality is likely to be expanding over time.

Supported LINQ constructs

Here’s a list of supported features

  • Select
  • SelectMany
  • Subqueries
  • Where
  • Skip/Take
  • Ordering
  • First/FirstOrDefault & Single/SingleOrDefault
  • Any/All
  • Count

Querying with LINQ

To start querying with Romantic Web the IEntityContext exposes the AsQuerable method. It comes in two forms

1
2
IQueryable<IEntity> untypedQuery = context.AsQueryable();
IQueryable<IPerson> typedQuery = context.AsQueryable<IPerson>();

Th first can be used to query any resource type. The second narrows the initial data set to instances of a given entity type (based on mapped RDF types). Here’s an equivalent of the second query with use of the non-generic method.

1
2
3
IQueryable<IEntity> untypedQuery = from entity in context.AsQueryable()
                                   where entity is IPerson
                                   select entity;

All further examples will use the generic overload.

Examples

All examples assume the following mappings

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[Class("foaf", "Person")]
public interface IPerson
{
    [Property("foaf", "givenName")]
    string Name { get; set; }

    [Property("foaf", "familyName")]
    string LastName { get; set; }

    [Collection("foaf", "nick")]
    ICollection<string> Nicknames { get; set; }

    [Collection("foaf", "knows")]
    ICollection<IPerson> Friends { get; set; }
}

Bare select

A simplest query possible is just a select.

1
2
from person in context.AsQueryable<IPerson>()
select person;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
SELECT ?s ?p ?o ?Gresources0 ?resources0
WHERE
{
    GRAPH ?Gresources0
    {
        ?s ?p ?o .
        ?resources0 a foaf:Person .
    }

    GRAPH <urn:meta:graph>
    {
        ?Gresources0 foaf:primaryTopic ?resources0 .
    }
}

There are a number of important facts about the above SPARQL query.

  1. The meta graph is used to limit the search to relevant Named Graphs
  2. Entire graphs are retrieved with an ?s ?p ?o pattern. This is to ensure a complete representation is always loaded
  3. Along the graph contents its URI and entity identifier are retrieved to keep track of where any given triple comes from. This is necessary, because SPARQL doesn’t allow CONSTRUCT queries, which return named graphs.

Filtering

The next simplest query is to use a where clause. The where clause transforms to an equivalent FILTER pattern in SPARQL.

1
2
3
from person in context.AsQueryable<IPerson>()
where person.Name == "Tim"
select person;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
SELECT ?s ?p ?o ?Gresources0 ?resources0
WHERE
{
    GRAPH ?Gresources0
    {
        ?s ?p ?o .
        ?resources0 a foaf:Person .
        ?resources0 foaf:givenName ?firstName0 .

        FILTER (?firstName0 = "Tim"^^xsd:string)
    }

    GRAPH <urn:meta:graph>
    {
        ?Gresources0 foaf:primaryTopic ?resources0 .
    }
}

A number of SPARQL operators are currently supported and more will be addded over time. Here’a more complex example of filtering the entities.

See how it is also possible to include the IEntity#Id property in the filtering rules.

1
2
3
4
5
from person in context.AsQueryable<IPerson>()
where person.Name == "Tim" || person.Name == "Tom"
where person.LastName.ToLower().StartsWith("berners")
where person.Id != new Uri("http://example.com/Other-Tim")
select person;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
SELECT ?s ?p ?o ?Gperson0 ?person0 
WHERE 
{ 
    GRAPH ?Gperson0 
    { 
        ?s ?p ?o . 
        ?person0 a foaf:Person . 
        ?person0 foaf:givenName ?firstName0 .
        ?person0 foaf:familyName ?surname0 .

        FILTER (?firstName0 = "Tim"^^xsd:string || ?firstName0 = "Tom"^^xsd:string)
        FILTER (CONTAINS(LCASE(?surname0),"lee"^^xsd:string))
        FILTER (?person0 != <http://example.com/Other-Tim>)
    }

    GRAPH <urn:meta:graph>
    {
        ?Gperson0 foaf:primaryTopic ?person0 .
    }
}

Ordering and slicing

Romantic Web’s LINQ provider supports ordering and slicing (the Skip and Take methods).

Ordering results in a subselect query, which selects identifiers and the the main query returns all Named Graphs’ contents for each of those entities.

1
2
3
(from person in context.AsQueryable<IPerson>()
 orderby person.Name descending
 select person).Skip(10).Take(5);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
SELECT ?s ?p ?o ?Gperson0 ?person0 
WHERE 
{ 
	{ 
		SELECT DISTINCT ?person0_sub 
		WHERE 
		{ 
			GRAPH ?Gperson0_sub 
			{ 
				?person0_sub a foaf:Person .
				?person0_sub foaf:givenName ?firstName0_sub . 
				?person0_sub foaf:familyName ?surname0_sub . 
			} 
			
			GRAPH <urn:meta:graph>
			{ 
				?Gperson0_sub foaf:primaryTopic ?person0_sub . 
			} 
		} 
		ORDER BY DESC(?firstName0_sub) ?surname0_sub 
		OFFSET 10 LIMIT 5 
	} 
	
	FILTER (?person0_sub=?person0) 
	
	GRAPH ?Gperson0 
	{ 
		?s ?p ?o . 
		?person0 a foaf:Person .
		?person0 foaf:givenName ?firstName0 . 
		?person0 foaf:familyName ?surname0 . 
	} 
	
	GRAPH <urn:meta:graph>
	{ 
		?Gperson0 foaf:primaryTopic ?person0 . 
	} 
} 
ORDER BY DESC(?firstName0) ?surname0

Subqueries

Subqueries can come in multiple forms in the LINQ syntax and Romantic Web allows various kinds of queries including multiple select clauses to select elements from subcollections and the use of Any/All methods.

The next query returns all persons, whose name is Henry and who are befriended by other persons.

1
2
3
4
from person in context.AsQueryable<IPerson>()
from friend in person.Friends
where friend.Name == "Marc"
select friend;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
SELECT ?s ?p ?o ?Gfriend0 ?friend0
WHERE 
{ 
	GRAPH ?Gperson0 
	{ 
		?person0 a foaf:Person .
		?person0 foaf:knows ?knows0 . 
	} 
	
	GRAPH <urn:meta:graph> 
	{
		?Gperson0 foaf:primaryTopic ?person0 . 
	} 
	
	GRAPH ?Gfriend0 
	{ 
		?s ?p ?o .
		?friend0 a foaf:Person .
		?friend0 foaf:givenName ?firstName0 . 
		
		FILTER (?firstName0 = "Marc"^^xsd:string)
	} 
	
	GRAPH <urn:meta:graph>
	{ 
		?Gfriend0 foaf:primaryTopic ?friend0 . 
	} 
}

And here’s an example of using Any, which can yield similar result but with a different SPARQL query.

1
2
3
from person in _entityContext.AsQueryable<IPerson>()
where person.Knows.Any(friend => friend.FirstName == "Marc")
select person
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
SELECT ?s ?p ?o ?Gperson0 ?person0 
WHERE 
{ 
    GRAPH ?Gperson0 
    { 
        ?s ?p ?o . 
        ?person0 a foaf:Person . 
        ?person0 foaf:knows ?friend0 . 
	
        FILTER (EXISTS 
        { 
            SELECT ?friend0 
            WHERE 
            { 
                GRAPH ?Gfriend0 
                { 
                    ?friend0 a foaf:Person . 
                    ?friend0 foaf:givenName ?firstName0 . 
		  
                    FILTER (?firstName0 = "Tomasz"^^xsd:string) 
                } 
		
                GRAPH <urn:meta:graph> 
                { 
                    ?Gfriend0 foaf:primaryTopic ?friend0 . 
                } 
            } 
        }) 
    } 
  
    GRAPH <urn:meta:graph> 
    { 
        ?Gperson0 foaf:primaryTopic ?person0 . 
    } 
}

Limitations

Not all features of LINQ are implemented such as

  • anonymous return types
  • returning new instances of other types