# 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.

## Data types overview

<table>
  <thead>
    <tr>
      <th colspan="1" scope="col">
        SurrealQL type
      </th>
      <th colspan="1" scope="col">
        Kind
      </th>
      <th colspan="2" scope="col">
        Documentation
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="1" scope="row">
        <code>string</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/en-us/dotnet/api/system.string">
          <code>String</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>int</code>, <code>float</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <span>Any number type, e.g. </span>
        <a href="https://learn.microsoft.com/en-us/dotnet/api/system.int32">
          <code>Int32</code>
        </a>
        <span>,</span>
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.single">
          <code>Single</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>bool</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/en-us/dotnet/api/system.boolean">
          <code>Boolean</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>null</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/null">
          <code>null</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>none</code>
      </td>
      <td colspan="1" scope="row">
        Custom
      </td>
      <td colspan="2" scope="row">
        <a href="/docs/reference/dotnet/data-types.md#none">
          <code>None</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>array</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        Any <code>IEnumerable</code> representation
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>object</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        Any <code>Object</code> representation
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>set</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        Any <code>HashSet</code> representation
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>datetime</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.datetime">
          <code>DateTime</code>
        </a>
        <span>or</span>
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.dateonly">
          <code>DateOnly</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>bytes</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <code>byte[]</code>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>uuid</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.guid">
          <code>Guid</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>duration</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.timespan">
          <code>TimeSpan</code>
        </a>
        <span>or</span>
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.timeonly">
          <code>TimeOnly</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>decimal</code>
      </td>
      <td colspan="1" scope="row">
        Native
      </td>
      <td colspan="2" scope="row">
        <a href="https://learn.microsoft.com/fr-fr/dotnet/api/system.decimal">
          <code>Decimal</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>geometry</code>
      </td>
      <td colspan="1" scope="row">
        via
        [Microsoft.Spatial](https://www.nuget.org/packages/Microsoft.Spatial)
      </td>
      <td colspan="2" scope="row">
        <code>Geometry</code> or <code>Geography</code> representations
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>range</code>
      </td>
      <td colspan="1" scope="row">
        Custom
      </td>
      <td colspan="2" scope="row">
        <a href="/docs/reference/dotnet/data-types.md#range">
          <code>Range</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>file</code>
      </td>
      <td colspan="1" scope="row">
        Custom
      </td>
      <td colspan="2" scope="row">
        <a href="/docs/reference/dotnet/data-types.md#surrealfile">
          <code>SurrealFile</code>
        </a>
      </td>
    </tr>
    <tr>
      <td colspan="1" scope="row">
        <code>record</code>
      </td>
      <td colspan="1" scope="row">
        Custom
      </td>
      <td colspan="2" scope="row">
        <a href="/docs/reference/dotnet/data-types.md#recordid">
          <code>RecordId</code>
        </a>
      </td>
    </tr>
  </tbody>
</table>

<br />
<br />

## `None`

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

```csharp title="Signature"
public readonly struct None { }
```

### Working with `None`

```csharp title="Constructing"
var none = new None();

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

<br />

## `Range`

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.

```csharp title="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.

```csharp title="Signature"
public readonly struct RecordIdRange<TStart, TEnd>
{
    public string Table { get; }

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

### Working with `Range`

```csharp title="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
```

<br />

## `SurrealFile`

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

```csharp title="Signature"
public readonly struct SurrealFile
{
    public string Bucket { get; }
    public string Path { get; } = "/";
}
```

### Working with `SurrealFile`

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

File references are returned when working with [file uploads](/docs/reference/query-language/language-primitives/data-types/files.md) and contain metadata about the stored file.

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

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

<br />

## `RecordId`

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.

```csharp title="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 deserialisation.

```csharp title="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`.

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

### Working with `RecordId`

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

```csharp title="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:

```csharp title="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:

```csharp title="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
```

### Extracting data

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

```csharp title="Simple record id"
RecordId rid = ("person", "john");
string table = rid.Table; // "person"
string id = rid.DeserializeId<string>(); // "john"
```

```csharp title="Record id with simple data type (other than string)"
RecordId rid = ("table", 42);
string table = rid.Table; // "table"
int id = rid.DeserializeId<int>(); // 42
```

```csharp title="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)
```

### Send back string

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.

```csharp title="Signature"
public class StringRecordId
{
    public string Value { get; }
}
```

### Working with a `StringRecordId`

```csharp title="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");
```

### Working with `RecordIdOfString`

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

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

### Working with `RecordIdOf<T>`

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

```csharp title="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.

### Inheriting from record

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

```csharp title="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", "…")
```

### Using data annotations

The SDK supports attributes for serialisation and deserialisation.

#### CBOR serialisation

Use [CborProperty](https://github.com/dahomey-technologies/Dahomey.Cbor) to map C# properties to SurrealDB fields:

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

#### RecordIdJsonConverter

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

```csharp title="Using RecordIdJsonConverter"
[RecordIdJsonConverter("payment_details")]
public RecordId? PaymentDetails { get; set; }

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

#### Combining attributes

You can combine both attributes on the same property:

```csharp title="Combining attributes"
[CborProperty("payment_details")]
[RecordIdJsonConverter("payment_details")]
public RecordId? PaymentDetails { get; set; }
```
