• Start

Languages

/

.NET

Data Types

The .NET SDK translates all datatypes native to SurrealQL into either datatypes native to .NET, or a custom implementation. This document describes all datatypes, and links to their respective documentation.

The .NET SDK translates datatypes native to SurrealQL into either datatypes native to .NET, or a custom implementation. This document describes all datatypes, and links to their respective documentation.

SurrealQL type Kind Documentation
string Native String
int, float Native Any number type, e.g. Int32 , Single
bool Native Boolean
null Native null
none Custom None
array Native Any IEnumerable representation
object Native Any Object representation
set Native Any HashSet representation
datetime Native DateTime or DateOnly
bytes Native byte[]
uuid Native Guid
duration Native TimeSpan or TimeOnly
decimal Native Decimal
geometry via Microsoft.Spatial Geometry or Geography representations
range Custom Range
file Custom SurrealFile
record Custom RecordId



The None type is a custom type that represents the absence of a value.

Signature

public readonly struct None { }

Constructing

var none = new None();

// Change the value of a record to None
var myRecord = new MyRecord();
myRecord.Value = new None();


A Range represents a bounded or unbounded range of values. Ranges are used in SurrealQL for selecting slices of records by ID or filtering numeric and temporal values.

Signature

public readonly struct Range<TStart, TEnd>
{
public RangeBound<TStart>? Start { get; }

public RangeBound<TEnd>? End { get; }
}

A RecordIdRange is a specialization for querying a range of records from a table.

Signature

public readonly struct RecordIdRange<TStart, TEnd>
{
public string Table { get; }

public Range<TStart, TEnd> Range { get; }
}

Constructing

var fullRange = Range.Full(); // Full range (no exclusion)

var startRange = Range.StartFrom<int>(new(1, RangeBoundType.Inclusive)); // Equivalent to 1..

var endRange = Range.EndTo<string>(new("x", RangeBoundType.Exclusive)); // Equivalent to .."x"

var explicitRange = Range.FromRange(2..10); // From C# Range


A SurrealFile represents a reference to a file stored in SurrealDB.

Signature

public readonly struct SurrealFile
{
public string Bucket { get; }
public string Path { get; } = "/";
}

Constructing

var ref = new SurrealFile("bucket", "/some/key/to/a/file.txt");

File references are returned when working with file uploads and contain metadata about the stored file.

var user = await db.Select(("user", "john"));

Console.WriteLine(user.Avatar.Bucket);
Console.WriteLine(user.Avatar.Path);


When you receive a RecordId back from SurrealDB, it will always be represented as a RecordId. The class holds Table and Id fields, representing the table name, and a unique identifier for the record on that table.

Signature

public class RecordId
{
public string Table { get; }

public T DeserializeId<T>();

// ... The rest is omitted for brevity
}

The RecordId is a non-generic class, allowing you to extract the Id field by providing the output type via the DeserializeId method. This can helpful when the RecordId is used in a generic context, for when you store the Id as an Object or an Array for example.

For cases where you are aware of the type of the Id field, you can use the generic version of RecordId to avoid the need for manual deserialization.

Signature with generics

public class RecordIdOf<T> : RecordId
{
public T Id { get; }
}

The default type of an Id in SurrealDB being a string, you can choose to use the default provided type RecordIdOfString.

Default RecordId

public class RecordIdOfString : RecordIdOf<string>
{
// The available properties, inherited from `RecordId` and `RecordIdOf<string>`
public string Table { get; }
public string Id { get; }
}

The simplest and most common way to construct a RecordId is with a tuple (table, id).

Constructing

// Table is "person"
// Unique identifier on the table is "john"
RecordId personId = ("person", "john");
// or
var personId = (RecordId)("person", "john");

This tuple is implicitly converted into a RecordId object. You can use it with all SDK methods:

Using RecordId

await db.Select<Person>(("person", "john"));
await db.Delete(("person", "john"));

You are not exclusively limited to the string type for the Id part. Several overloads exist for different id types:

Constructing

RecordId rid1 = ("person", "alice");           // string
RecordId rid2 = ("person", 123); // int
RecordId rid3 = ("person", 123L); // long
RecordId rid4 = ("person", (short)5); // short
RecordId rid5 = ("person", (byte)7); // byte
RecordId rid6 = ("person", Guid.NewGuid()); // Guid

The .NET SDK handles serialization and deserialization of the Table and Id parts in Record Id. The serialization is done automatically when sending data to the server. However, deserialization may need to be done manually according to the data type of the Id field. Below are some examples:

Simple record id

RecordId rid = ("person", "john");
string table = rid.Table; // "person"
string id = rid.DeserializeId<string>(); // "john"

Record id with simple data type (other than string)

RecordId rid = ("table", 42);
string table = rid.Table; // "table"
int id = rid.DeserializeId<int>(); // 42

Record id with complex data types

var rid = new RecordIdOf<CityId>("table", new CityId { City = "London" });
var id = rid.DeserializeId<CityId>(); // CityId { City = "London" }

var rid = new RecordIdOf<(string, int)>("table", ("London", 42));
var id = rid.DeserializeId<(string, int)>(); // ("London", 42)

If you need to send back a Record Id in string format, you can do so with the StringRecordId class.

We do not implement the parsing of Record Ids in the .NET SDK, as that would mean that we need to be able to parse any SurrealQL value, which comes with a cost. Instead you can send it over as a string with StringRecordId, allowing the server to handle the parsing.

Signature

public class StringRecordId
{
public string Value { get; }
}

Constructing

// Table is "person"
// Unique identifier on the table is "john"
var rid = new StringRecordId("person:john");

// Alternatively, a StringRecordId can be inferred explicitly from a string
var rid = (StringRecordId)"person:john";
await client.Select<Person>((StringRecordId)"person:john");

For string-based identifiers, you can also use the specialized type RecordIdOfString:

Using RecordIdOfString

var rid = new RecordIdOfString("person", "john");
// or
Console.WriteLine(rid.Table); // "person"
Console.WriteLine(rid.Id); // "john"

For complex or structured identifiers, use the generic type RecordIdOf\<T\>:

Using RecordIdOf<T>

public class CityId
{
public string City { get; set; } = string.Empty;
}

var rid = new RecordIdOf<CityId>("city", new CityId { City = "London" });

This enables strongly-typed IDs that map directly to your domain objects.

If your model class inherits from Record, it will automatically include an Id property of type RecordId.

Inheriting from Record

public class Person : Record
{
public string Name { get; set; } = string.Empty;
}

// Example usage
var person = new Person { Name = "Alice" };
Console.WriteLine(person.Id); // RecordId ("person", "…")

The SDK supports attributes for serialization and deserialization.

Use CborProperty to map C# properties to SurrealDB fields:

Using CborProperty

[CborProperty("first_name")]
public string FirstName { get; set; } = string.Empty;

Use RecordIdJsonConverter to indicate that a property should be serialized as a RecordId reference to another table:

Using RecordIdJsonConverter

[RecordIdJsonConverter("payment_details")]
public RecordId? PaymentDetails { get; set; }

[RecordIdJsonConverter("payment_details")]
public RecordId? PaymentDetails { get; set; }

You can combine both attributes on the same property:

Combining attributes

[CborProperty("payment_details")]
[RecordIdJsonConverter("payment_details")]
public RecordId? PaymentDetails { get; set; }

Was this page helpful?