objects |> functions


Uno - Step 3

Our implementation here will fall into two broad categories - configuring the connection and adding it to the ASP.NET Core's DI container, then adding the indexing code. Before we get to that, though, we need to add some dependencies. To paket.dependencies, add:

1: 
2: 
3: 
4: 
nuget Microsoft.Extensions.Configuration.FileExtensions
nuget Microsoft.Extensions.Configuration.Json
nuget Microsoft.Extensions.Options.ConfigurationExtensions
nuget RavenDb.Client

Then, add these 4 packages to paket.references in Uno and run paket install to download and install these new dependencies.

Create the Database

If you run RavenDB in interactive mode, it should launch a browser with RavenDB Studio; if you have it running as a service on your local machine, go to http://localhost:8080. Using the studio, create a database called "O2F1".

Configuring the Connection and Adding to DI

We will store our connection settings with the other configuration for the application. The standard .NET Core name for such a file is appsettings.json, so we create one with the following values:

1: 
2: 
3: 
4: 
5: 
6: 
{
  "RavenDB": {
    "Url": "http://localhost:8080",
    "Database": "O2F1"
  }
}

We also need to tell the compiler that, although this file doesn't need to be compiled, it does need to be copied to the output directory. We can add the following group to Uno.csproj, just below the import for the Paket targets file:

1: 
2: 
3: 
4: 
5: 
<ItemGroup>
    <None Update="appsettings.json">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
</ItemGroup>

When we were doing our quick-and-dirty "Hello World" in step 1, we had very minimal content in Startup.cs. Now, we'll flesh that out a little more.

 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: 
[add]
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Raven.Client.Documents;
[/add]

public class Startup
{
    [add]
    public static IConfigurationRoot Configuration { get; private set; }
    
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }
    
    public void ConfigureServices(IServiceCollection services)
    {
        var cfg = Configuration.GetSection("RavenDB");
        var store = new DocumentStore
        {
            Urls = new[] { cfg["Url"] },
            Database = cfg["Database"]
        };
        services.AddSingleton(store.Initialize());
    }
    [/add]

This does the following:

  • In the constructor, creates a configuration tree that is a union of appsettings.json, appsettings.{environment}.json, and environment variables (each of those overriding the prior one if settings are specified in both)
  • In ConfigureServices, gets the RavenDB configuration sections, uses it to configure the DocumentStore instance, and registers the output of its Initialize method as the IDocumentStore singleton in the DI container.

We'll come back to this file, but we need to write some more code first.

Defining Collections

RavenDB creates document collection names using the plural of the name of the type being stored - ex., a Post would go in the Posts collection. Its Ids also follow the form [collection]/[id], so post 123 would have the document Id Posts/123. Data/Collection.cs contains C# constants we will use to reference our collections. It also contains two utility methods: one for creating a document Id from a collection name and a Guid, and the other for deriving the collection name and Id from a document Id.

Ensuring Indexes Exist

RavenDB provides a means of creating strongly-typed indexes as classes that extend AbstractIndexCreationTask<T>; these definitions can be used to both define and query indexes. We will create these in the Uno.Data.Indexes namespace. You can review all the files there, but we'll look at one example here.

The naming convention for indexes within RavenDB is [collection]/By[field]. The index description below defines an index that allows us to query categories by web log Id and slug.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
using Raven.Client.Documents.Indexes;
using System.Linq;
using Uno.Domain;

namespace Uno.Data.Indexes
{
    public class Categories_ByWebLogIdAndSlug : AbstractIndexCreationTask<Category>
    {
        public Categories_ByWebLogIdAndSlug()
        {
            Map = categories => from category in categories
                                select new
                                {
                                    category.WebLogId,
                                    category.Slug
                                };
        }
    }
}

Now, let's revisit Startup.cs. The RavenDB client has a nice feature where it will scan assemblies for these indexes, and automatically create them. We'll use the name of this index to accomplish the registration.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
[add]
using Raven.Client.Documents.Indexes;
using Uno.Data.Indexes;
[/add]

[in ConfigureServices(), after the call to .AddSingleton()]
    IndexCreation.CreateIndexes(typeof(Categories_ByWebLogIdAndSlug).Assembly, store);
[/end]

Now, if we build and run our application, then use RavenDB studio to look at the indexes for the O2F1 database, we should be able to see the indexes we specified.


Back to Step 3

namespace Microsoft
union case Option.None: Option<'T>
Fork me on GitHub