RDF - Object Mapping for RDF in .net

MakoLab open-source component

Create entity context

Now that we have a entity context factory created in the previous step it is time to create an entity context. It is as simple as calling one method.

1
var context = contextFactory.CreateContext();

Creating entities

We are now ready to start creating some data. Here’s how we create a new entity and populate it with some data

1
2
3
4
5
6
7
8
9
// create an entity
var tim = context.Create<IPerson>(new Uri("Tim-Berners-Lee"));

// set some properties
tim.Name = "Tim";
tim.LastName = "Berners-Lee";

// commit data to store
context.Commit();

This will create some triples describing sir Tim Berners-Lee (assuming mappings from the previous page)

1
2
3
<Tim-Berners-Lee>
    foaf:givenName "Tim" ;
    foaf:LastName "Berners-Lee" .

You may notice that for simplicity relative URIs are used. This is totally supported by Romantic Web, but requires configuration. Please read more on the detailed page about entity context factory configuration.

Loading entities

The entity context is also used to retrieve entities. Its interface exposes a generic Load method, which creates an instance of the given entity type.

For example later on we may want to associate sir Tim Berners-Lee with another person we create.

1
2
3
4
5
6
var norman = context.Create<IPerson>(new Uri("Norman-Walsh"));
var tim = context.Load<IPerson>(new Uri("Tim-Berners-Lee"));

tim.Knows.Add(norman);

context.Commit();

This would assert the following triple in the store

1
<Tim-Berners-Lee> foaf:knows <Norman-Walsh> .

Inheritance

It would be common for entity types to derive from one another. For example if we were to map the foaf vocabulary, there would be an IPerson and an IAgent types, matching the equivalent RDF classes. Moreover the Person class is defined as subclass of the class Agent. Hence the mapping would look like below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
using RomanticWeb.Mapping.Attributes;
using RomanticWeb.Mapping.Model;

[Class("foaf", "Person")]
public interface IPerson : IEntity
{
}

[Class("foaf", "Agent")]
public interface IAgent : IEntity
{
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
using RomanticWeb.Mapping.Fluent;

public class PersonMap : EntityMap<IPerson>
{
    public PersonMap()
    {
        Class.Is("foaf", "Person");
    }
}

public class AgentMap : EntityMap<IAgent>
{
    public AgentMap()
    {
        Class.Is("foaf", "Agent");
    }
}

In such setup loading loading any entity would create an instance of a correct most derived type. Thus the code below works. This works even if we decide to load the the entity as the base type IEntity.

1
2
3
4
5
EntityId identifier = new Uri("Tim-Berners-Lee");
var sirTim = context.Load<IEntity>(identifier);

Assert.That(sirTim is IPerson);
Assert.That(sirTim is IAgent);

NOTE The above is of course true only if relevant rdf:type triples are present in the store.

Deleting entities

Lastly of course there is a Delete method, which removes all triples about a given entity. Because entity data is partitioned in Named Graphs, deletion simply removes whole graphs. This means that there are no blank nodes orphaned if the resource had any in its representation. The usage is a s simple as

1
2
EntityId identifier = new Uri("Tim-Berners-Lee");
context.Delete(identifier);

And if you have an entity instance you’d do

1
2
IPerson tim = //...
context.Delete(tim.Id);

Removing references

By default deleting an entity will only remove its representation, but if it referenced in other graphs, these references will remain. For example given some quads describing two entities

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<about:Tim-Berners-Lee>
{
    <Tim-Berners-Lee>
        foaf:givenName "Tim" ;
        foaf:LastName "Berners-Lee" .
}

<about:Norman-Walsh>
{
    <Norman-Walsh>
        foaf:givenName "Norman" ;
        foaf:LastName "Walsh" ;
        foaf:knows <Tim-Berners-Lee>.
}

calling context.Delete("Tim-Berners-Lee") will not remove the foaf:knows triple from the second graph. To do that a second parameter must be passed to that method

1
2
IPerson tim = //...
context.Delete(tim.Id, DeleteBehaviour.NullifyChildren);

In RDF terms, it will remove all triples where the deleted entity is the object.

Transforming entities

One consequence of open-world assumption is that each resource can be an instance of many types at the same time. It poses a challenge to the statically typed languages like C# where an instance cannot change its type at will.

In Romantic Web each entity is a proxy to the underlying triples and it is possible to change the object from one type to another without any restrictions on the types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
using RomanticWeb;

IEntityContext context = // ...
Uri personUri = new Uri("http://example.org/ThePerson");

var person = context.Create<IPerson>(personUri);
person.Name = "John";
person.LastName = "Doe";

var employee = person.AsEntity<IEmployee>();
employee.Salary = 2000;
employee.EmployerName = "cool soft inc";

context.Commit();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using RomanticWeb.Mapping.Attributes;
using RomanticWeb.Mapping.Model;

public interface IPerson : IEntity
{
    [Property("foaf", "givenName")]
    string Name { get; set; }

    [Property("foaf", "familyName")]
    string LastName { get; set; }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using RomanticWeb.Mapping.Attributes;
using RomanticWeb.Mapping.Model;

public interface IEmployee : IEntity
{
    [Property("ex", "salary")]
    int Salary { get; set; }

    [Property("ex", "employer")]
    string EmployerName { get; set; }
}
1
2
3
4
5
<http://example.org/ThePerson>
    foaf:givenName "John"^^xsd:string ;
    foaf:familyName "Doe"^^xsd:string ;
    ex:salary 2000 ;
    ex:employer "cool soft inc"^^xsd:string .

As the example shows, the resource representation contains all triples that are part of the person and employee representations.