C# driver

C# logo

Fauna’s open source C# driver provides the resources required to interact with Fauna for C# and Mono developers.

Current stable version

4.2.0

Repository

This section provides a high-level overview of working with the driver. For details on the driver’s API, see its documentation.

Installation

Install the Nuget package by adding the package reference to your MSBuild project:

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp3.1</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="FaunaDB.Client" Version="4.2.0" />
    </ItemGroup>
</Project>

or by using your IDE and searching for FaunaDB.Client.

Usage

Here is an example demonstrating how to use the C# driver to execute a simple query on Fauna:

Use the correct endpoint value for your database’s Region Group.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FaunaDB.Client;
using FaunaDB.Types;

using static FaunaDB.Query.Language;

namespace FaunaDBProject
{
    class FaunaDBHelloWorld
    {
        static readonly string ENDPOINT = "https://db.fauna.com:443";
        // NOTE: use the correct endpoint for your database's Region Group.
        static readonly string SECRET = "<<YOUR-SECRET-HERE>>";

        static void ProcessData(Value[] values)
        {
            foreach (Value value in values)
            {
                //do something
            }
        }
        static async Task DoQuery(FaunaClient client)
        {
            Value result = await client.Query(Paginate(Match(Index("spells"))));
            IResult<Value[]> data = result.At("data").To<Value[]>();

            data.Match(
                Success: value => ProcessData(value),
                Failure: reason => Console.WriteLine($"Something went wrong: {reason}")
            );
        }

        public static void Main(string[] args)
        {
            var client = new FaunaClient(endpoint: ENDPOINT, secret: SECRET);

            DoQuery(client).Wait();
        }
    }
}

How to instantiate a Fauna FaunaClient

var client = new FaunaClient(
    endpoint: ENDPOINT,
    secret: SECRET,
    httpClient: HTTP_CLIENT,
    timeout: TIMEOUT
);

secret is the only required argument. All other arguments are optional.

You can also pass a custom HttpClient when creating a new FaunaClient:

// using System.Net.Http;
var http = new HttpClient();

// The default request headers can be any string values,
// but should be specific to your application.
http.DefaultRequestHeaders.Add("X-Custom-Header", "42");

http.Timeout = TimeSpan.FromSeconds(15);

var client = new FaunaClient(
    "secret",
    "http://localhost:9090/",
    httpClient: http
);

See Connections for more details on creating client connections.

How to execute a query

Value result = await client.Query(Paginate(Match(Index("spells"))));

The Query method receives an Expr object. Expr objects can be composed with others Expr to create complex query objects. FaunaDB.Query.Language is a helper class where you can find all available expressions in the library.

You can also pass a TimeSpan queryTimeout argument as a second parameter when calling Query:

Value result = await client.Query(
    Paginate(Match(Index("spells"))),
    TimeSpan.FromSeconds(42)
);

The queryTimeout has a resolution of milliseconds. When the timeout period has elapsed, the server terminates the query and returns an error.

How to access objects fields and convert to primitive values

Objects fields are accessed through At methods of Value class. It’s possible to access fields by names if the value represents an object or by index if it represents an array. Also, it’s possible to convert Value class to its primitive correspondent using To methods specifying a type.

IResult<Value[]> data = result.At("data").To<Value[]>();

How work with IResult<T> objects

This object represents the result of an operation and it might be success or a failure. All conversion operations returns an object like this. This way it’s possible to avoid check for null-ability everywhere in the code.

data.Match(
    Success: value => ProcessData(value),
    Failure: reason => Console.WriteLine($"Something went wrong: {reason}")
);

Optionally it’s possible transform one IResult<T> into another IResult<U> of different type using Map and FlatMap.

IResult<int> result = <<...>>;
IResult<string> result.Map(value => value.toString());

If result represents an failure all calls to Map and FlatMap are ignored. See FaunaDB.Types.Result.

How to work with user-defined classes

Instead of manually creating your objects via the DSL (e.g. the Obj() method), you may use the Encoder class to convert a user-defined type into the equivalent Value type.

For example:

class Product
{
    [FaunaField("description")]
    public string Description { get; set; }

    [FaunaField("price")]
    public double Price { get; set; }

    [FaunaConstructor]
    public Product(string description, double price)
    {
        Description = description;
        Price = price;
    }
}

To persist an instance of Product in Fauna:

Product product = new Product("Smartphone", 649.90);

await client.Query(
    Create(
        Collection("product"),
        Obj("data", Encoder.Encode(product))
    )
);

To convert from a Value type back to the Product type, you can use a Decoder:

Value value = await client.Query(Get(Ref(Collection("product"), "123456789")));

Product product = Decoder.Decode<Product>(value.At("data"));

or via the To<T>() helper method:

Value value = await client.Query(Get(Ref(Collection("product"), "123456789")));

IResult<Product> product = value.At("data").To<Product>();
product.Match(
    Success: p => Console.WriteLine("Product loaded: {0}", p.Description),
    Failure: reason => Console.WriteLine($"Something went wrong: {reason}")
);

// or even:

Product productLoaded = value.At("data").To<Product>().Value;
Console.WriteLine("Product loaded: {0}", prod.Description);

Note that in this case the return type is IResult<T>.

There are three attributes that can be used to change the behavior of the Encoder and Decoder:

  • FaunaField: Used to override a custom field name and/or provide a default value for that field. If this attribute is not specified, the member name will be used instead. Can be used on fields, properties and constructor arguments.

  • FaunaConstructor: Used to mark a constructor or a public static method as the method used to instantiate the specified type. This attribute can be used only once per class.

  • FaunaIgnore: Used to ignore a specific member. Can be used on fields, properties and constructors arguments. If used on a constructor argument, that argument must have a default value.

Encoder and Decoder can currently convert:

  • Primitive scalar types (int, long, string, etc.)

  • Primitive arrays, generic collections such as List<T>, and their respective interfaces such as IList<T>.

  • Dictionaries with string keys, such as Dictionary<string, T> and its respective interface IDictionary<string, T>.

Event streaming

This section demonstrates how to subscribe to change events. To learn more, see Event streaming.

The streaming API is built using the Observer pattern, which enables a subscriber to register with and receive notifications from a provider. The provider is implemented within the StreamingEventHandler class, and the subscriber is implemented within the StreamingEventMonitor class.

There are two kinds of event streaming:

The code required to subscribe to each type is very similar. The primary difference is the type of Reference involved in the subscription, and the kinds of events that are included in the stream.

There is a cost in compute operations to hold a stream open, or to repeatedly attempt to start a stream that fails for any reason.

See Billing for details.

Document streaming

The following example subscribes to change events for a specific document:

            var client = new FaunaClient(
                endpoint: endpoint,
                secret: secret
            );

            // Define a reference to the document that we want to stream
            // Note that the Scores collection must already exist
            var docRef = Ref(Collection("Scores"), "1");

            // Define a stream provider
            var provider = await client.Stream(docRef);

            // Define a task object that represents the application
            // state for streaming events.
            var done = new TaskCompletionSource<object>();

            // Create a subscriber.
            // It takes three callback functions:
            // - the callback for the next event
            // - the callback for errors
            // - the callback for when the stream terminates
            var monitor = new StreamingEventMonitor(
                value => {
                    Console.WriteLine(value);
                    provider.RequestData();
                },
                exception => { done.SetException(exception); },
                () => { done.SetResult(null); }
            );

            // Subscribe to the data provider
            monitor.Subscribe(provider);

            // Block until we receive all of the events. For this
            // example, we wait indefinitely. Use Ctrl-C to terminate.
            await done.Task;

            // Clean up the subscription, after it terminates.
            monitor.Unsubscribe();

Before you run the example:

  1. Be sure to set the FAUNADB_SECRET environment variable, and optionally the FAUNADB_ENDPOINT environment variable (if you are using Region Groups or Fauna Dev).

  2. The collection Scores must exist.

Once the example is running, you can use the Fauna Dashboard, or another client application, to create or modify the target document and watch the events arrive as the changes are made.

For example, if the document does not yet exist, you could run this query in the Fauna Dashboard's Shell:

Create(Ref(Collection("Scores"), "1"), { data: { scores: [1, 2, 3] }})

Once the document exists, you could run this query:

Update(Ref(Collection("Scores"), "1"), { data: { scores: [5, 2, 3] }})

The streaming example waits indefinitely for events. Use Ctrl+C to terminate the program.

You can also extend a base class instead of passing lambda functions:

private class MyStreamingMonitor : StreamingEventMonitor
{
    // optionally override OnNext event
    public override void OnNext(Value value)
    {
        // process your event
        RequestData();
    }

    // optionally override OnError event
    public override void OnError(Exception error)
    {
        // process an error
    }

    // optionally override OnCompleted event
    public override void OnCompleted()
    {
        // process completion event
    }
}

// creating a subscriber
var monitor = new MyStreamingMonitor();

// subscribe to data provider
monitor.Subscribe(provider);

// clear the subscription
monitor.Unsubscribe();

Set streaming

The following example subscribes to change events for a set:

            var client = new FaunaClient(
                endpoint: endpoint,
                secret: secret
            );

            // Define the reference to the target set
            var setRef = Documents(Collection("Scores"));

            // Define the stream fields to include
            var streamOptions = new List<EventField>{
                EventField.ActionField,
                EventField.DocumentField,
                EventField.IndexField
            };

            // Define a stream provider
            var provider = await client.Stream(
                setRef,
                fields: streamOptions
            );

            // Define a task object that represents the application
            // state for streaming events.
            var done = new TaskCompletionSource<object>();

            // Create a subscriber.
            // It takes three callback functions:
            // - the callback for the next event
            // - the callback for errors
            // - the callback for when the stream terminates
            var monitor = new StreamingEventMonitor(
                value => {
                    Console.WriteLine(value);
                    provider.RequestData();
                },
                exception => { done.SetException(exception); },
                () => { done.SetResult(null); }
            );

            // Subscribe to the data provider
            monitor.Subscribe(provider);

            // Block until we receive all of the events. For this
            // example, we wait indefinitely. Use Ctrl-C to terminate.
            await done.Task;

            // Clean up the subscription, after it terminates.
            monitor.Unsubscribe();

Before you run the example:

  1. Be sure to set the FAUNADB_SECRET environment variable, and optionally the FAUNADB_ENDPOINT environment variable (if you are using Region Groups or Fauna Dev).

  2. The collection Scores must exist.

Once the example is running, you can use the Fauna Dashboard, or another client application, to add or delete documents in the "Scores" collection and watch the events arrive as the changes are made. For example, you could run this query in the Fauna Dashboard's Shell:

Create(Collection("Scores"), { data: { scores: [5, 6, 7] }})

The streaming example waits indefinitely for events. Use Ctrl+C to terminate the program.

You can also extend a base class instead of passing lambda functions:

private class MyStreamingMonitor : StreamingEventMonitor
{
    // optionally override OnNext event
    public override void OnNext(Value value)
    {
        // process your event
        RequestData();
    }

    // optionally override OnError event
    public override void OnError(Exception error)
    {
        // process an error
    }

    // optionally override OnCompleted event
    public override void OnCompleted()
    {
        // process completion event
    }
}

// creating a subscriber
var monitor = new MyStreamingMonitor();

// subscribe to data provider
monitor.Subscribe(provider);

// clear the subscription
monitor.Unsubscribe();

Next steps

Was this article helpful? 

We're sorry to hear that.
Tell us how we can improve!
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!