Accessing Stardog from .NET

Learn how to access Stardog data using Trinity RDF.

Accessing Stardog from .NET!

I’m a .NET developer and I want to use familiar tools like Entity Framework to connect to an RDF data source like Stardog. I’m not even sure what Stardog is?

Developers wanting to connect to Stardog and query it using .NET can use Trinity RDF. Advertising itself as the “Entity Framework for Graph Databases”, Trinity RDF enables .NET developers to use RDF data in their apps without ever writing a line of SPARQL. The package allows them to create classes that map C# objects to RDF concepts. Developers can use familiar technologies, like LINQ, to query and manipulate the data.

To get started you need a running instance of Stardog, a new C# project and the Trinity RDF NuGet package.

Connect to Stardog

You can establish a connection to a Stardog server by passing a connection string with the provider key set to stardog and the required host, sid, and credential keys to the CreateStore method of the StoreFactory class:

// make Trinity aware of ontologies and mapped classes in our assembly
OntologyDiscovery.AddAssembly(Assembly.GetExecutingAssembly());
MappingDiscovery.RegisterCallingAssembly();

// TODO: Change the connection string for your Stardog instance's host, port and credentials
const string connectionString = "provider=stardog;host=http://localhost:5820;uid=admin;pw=admin;sid=music";

// Load the stardog store provider
StoreFactory.LoadProvider<StardogStoreProvider>();

// Connect to the stardog store
IStore store = StoreFactory.CreateStore(connectionString);

Making Trinity RDF aware of our ontologies

Trinity RDF is made aware of ontologies via a ontologies.config file.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <ontologies namespace="example" >

        <!--http://www.w3.org/1999/02/22-rdf-syntax-ns#-->
        <ontology uri="http://www.w3.org/1999/02/22-rdf-syntax-ns#" prefix="rdf">
        <filesource location="ontologies/rdf.ttl"/>
        </ontology>

        <!--http://www.w3.org/2000/01/rdf-schema#-->
        <ontology uri="http://www.w3.org/2000/01/rdf-schema#" prefix="rdfs">
        <filesource location="ontologies/rdfs.ttl"/>
        </ontology>

        <ontology uri="http://stardog.com/tutorial/" prefix="music">
            <filesource location="ontologies/music_schema.ttl"/>
        </ontology>

  </ontologies>
</configuration>

We can use the ontolgies.config file to initialize our Stardog connection via the InitializeFromConfiguration method of our store’s instance.

store.InitializeFromConfiguration(Path.Combine(Environment.CurrentDirectory, "ontologies.config"));

Create object model classes

Trinity RDF reads the ontologies.config file and uses it to generate annotations which we can use to create object model classes that map to RDF classes.

Below is an example object model for a Band RDF class:

using example;
using Semiodesk.Trinity;
using System;
using System.Collections.Generic;

namespace TrinityConsoleSample.ObjectModels
{
    /// <summary>
    /// A music group; that is, is a group of people creating and performing music together.
    /// </summary>
    [RdfClass(MUSIC.Band)]
    class Band : Artist
    {
        #region Members

        /// <summary>
        /// A member of a band. Does not distinguish between past vs current members
        /// </summary>
        [RdfProperty(MUSIC.member)]
        public List<SoloArtist> Members { get; set; }

        #endregion

        #region Constructors

        public Band(Uri uri) : base(uri) { }

        #endregion

    }
}

Note the annotations [RDFClass(MUSIC.Band)] on the Band class definition and [RDFProperty(MUSIC.member)] on the Members property definition which were generated by Trinity RDF (the annotations are generated in a pre-build step added by Trinity RDF. The generated code can be found in a file called Ontologies.g.cs).

We can work directly with named graphs using Trinity RDF’s Model concept - a model represents a logical grouping of data which allows us to execute queries directly against that data.

Query data using LINQ

Once we create our object models and a connection we can use LINQ to query the data. We can take advantage of Stardog features like reasoning by passing a boolean flag to the Model’s AsQueryable method:

var artists = Model.AsQueryable<Artist>(true);

Console.WriteLine($"With reasoning set to 'true', the artists query returned {artists.Count()} records");

To page through the bands in our sample music database, we can use the following code:

const int PAGE_SIZE = 4;
const int MAX_NUMBER_OF_PAGES = 8;

// NOTE: Creating a Queryable<> does not issue a query. Queries are created and executed in a
// lazy fashion when you access the results, depending on your requested result type:
//
// Default: SPARQL SELECT
// Count(): SPARQL SELECT COUNT(x)
// Any(): SPARQL ASK
var allBands = Model.AsQueryable<Band>()
                    .OrderBy((b) => b.Name);

// loop through pages of bands
for (int n = 0; n < MAX_NUMBER_OF_PAGES; n++)
{
    Console.WriteLine($"Fetching {PAGE_SIZE} bands from page {n + 1}...");

    foreach (Band b in allBands.Skip(n * PAGE_SIZE).Take(PAGE_SIZE))
    {
        Console.WriteLine($"Band: {b.Name}");

        foreach (SoloArtist sa in b.Members)
        {
            Console.WriteLine($"\tMember: {sa.Name}");
        }
    }
}

We can find bands whose names contain a string by using LINQ’s Where method:

// NOTES ON LINQ:
// 1. Try to avoid calling .Count(), .Any(), .First(), etc. on queryables as much as possible because
// these will result in SPARQL queries to be issued.
// 2. Calling .ToList() here is a potential performance issue depending on the number of results.
// The benefit here is that now,
// .Any() and .First() are invoked upon a list in memory and do not result in additional SPARQL queries.
var matchingBands = Model.AsQueryable<Band>()
    .Where(band => band.Name.StartsWith("the beatles", StringComparison.InvariantCultureIgnoreCase))
    .ToList();

Console.WriteLine($"Found {matchingBands.Count} band(s) that match!");

And then we can get the albums from the first band that matched our criteria:

var firstBand = matchingBands.First();

// Get all the albums by the first band from our result set...
var albums = Model.AsQueryable<Album>().Where(album => album.Artist == firstBand).ToList();

Console.WriteLine($"Band {firstBand.Name} has {albums.Count} albums");

We can grab a random album from the band’s discography and print out its tracks, their length and who wrote them:

var rand = new Random();
var randomAlbum = albums.Skip(rand.Next(albums.Count)).First();

Console.WriteLine($"Track list for album '{randomAlbum.Name}' by {randomAlbum.Artist.Name}:");

foreach (var track in randomAlbum.Tracks)
{
    Console.WriteLine($"\tTrack: {track.Name}, Length: {TimeSpan.FromSeconds(track.Length).TotalMinutes.ToString("0.##")}");

    foreach (var writer in track.Writers)
    {
        Console.WriteLine($"\t\tWritten by: {writer.Name}");
    }
}

Finally, we can total the track lengths to find out how long the album is…

int totalLength = randomAlbum.Tracks.Sum(s => s.Length);

Console.WriteLine($"Total length is {TimeSpan.FromSeconds(totalLength).TotalMinutes.ToString("0.##")}");

And we did all of that using LINQ!

Seeing generated SPARQL queries

However, if you do want to see the SPARQL queries that Trinity RDF generates for us we can pass a handler to our store instance’s Log property:

// this will write all the SPARQL queries to STDERR
store.Log = (query) => Console.Error.WriteLine(query);

Final Thoughts

You can use tools like Trinity RDF to make Stardog data available to a wider community of developers and end users.

The code in this sample uses the sample Stardog music database

You can find the complete code sample in the stardog-examples GitHub Repository


Applied
DotNet
Trinity RDF

Read Next