Using Dapper
Dapper is a micro ORM (Object-Relational Mapping) tool that provides a simple and flexible way to interact with databases. It operates on top of the ADO.NET standard and offers various features that simplify database operations.
ADO.NET
ADO.NET is a set of classes that provide developers with data access using the .NET Framework platform.
The YDB SDK for C# offers a set of classes that implement the ADO.NET standard.
Installation
The ADO.NET implementation for YDB is available via NuGet.
dotnet add package Ydb.Sdk
Creating a connection
A connection to YDB is established using YdbConnection
.
-
Using the parameterless constructor:
The following code creates a connection with the default settings:
await using var ydbConnection = new YdbConnection(); await ydbConnection.OpenAsync();
This option creates a connection to the database at the URL
grpc://localhost:2136/local
with anonymous authentication. -
Using the constructor with a connection string:
In the following example, a connection is created using a connection string in ADO.NET:
await using var ydbConnection = new YdbConnection( "Host=database-sample-grpc;Port=2135;Database=/root/database-sample"); await ydbConnection.OpenAsync();
In this case, the connection is established at the URL
grpc://database-sample-grpc:2135/root/database-sample
. When using the connection string method, parameters are specified as key-value pairs, separated by semicolons (key1=value1;key2=value2
). The supported set of keys is explained below. -
Using the constructor with a
YdbConnectionStringBuilder
argument:The example using
YdbConnectionStringBuilder
is demonstrated in the code below:var ydbConnectionBuilder = new YdbConnectionStringBuilder { Host = "server", Port = 2135, Database = "/ru-prestable/my-table", UseTls = true }; await using var ydbConnection = new YdbConnection(ydbConnectionBuilder); await ydbConnection.OpenAsync();
Connection parameters
All available connection parameters are defined as properties in the YdbConnectionStringBuilder
.
Here is a list of parameters that can be specified in the connection string:
Parameter | Description | Default value |
---|---|---|
Host |
Specifies the YDB server host | localhost |
Port |
Specifies the YDB server port | 2136 |
Database |
Specifies the database name | /local |
User |
Specifies the username | Not defined |
Password |
Specifies the user password | Not defined |
UseTls |
Indicates whether to use the TLS protocol (grpcs or grpc ) |
false |
MaxSessionPool |
Specifies the maximum session pool size | 100 |
RootCertificate |
Specifies the path to the trusted server TLS certificate. If this parameter is set, UseTls will be true |
Not defined |
There are also additional parameters that do not participate in forming the connection string. These can only be specified using YdbConnectionStringBuilder
:
Parameter | Description | Default value |
---|---|---|
LoggerFactory |
This parameter accepts an instance that implements the ILoggerFactory interface. The ILoggerFactory is a standard interface for logging factories in .NET. It is possible to use popular logging frameworks such as NLog, serilog, log4net |
NullLoggerFactory.Instance |
CredentialsProvider |
An authentication provider that implements the Ydb.Sdk.Auth.ICredentialsProvider . YDB SDK provides several standard ways for authentication: 1) Ydb.Sdk.Auth.AnonymousProvider . Anonymous YDB access, mainly for tests purposes. 2) Ydb.Sdk.Auth.TokenProvider . Token authentication for OAuth-like tokens. 3) Ydb.Sdk.Auth.StaticCredentialsProvider . Username and password based authentication. For Yandex.Cloud specific authentication methods, consider using ydb-dotnet-yc |
AnonymousProvider |
Usage
The YdbCommand
object is used to execute queries:
await using var ydbConnection = new YdbConnection();
await ydbConnection.OpenAsync();
var ydbCommand = ydbConnection.CreateCommand();
ydbCommand.CommandText = "SELECT 'Hello, World!'u";
Console.WriteLine(await ydbCommand.ExecuteScalarAsync());
This example demonstrates how to run the Hello, World!
query and output its result to the console.
Transactions
To create a client transaction, use the ydbConnection.BeginTransaction()
method.
There are two signatures of this method with a single isolation level parameter:
-
BeginTransaction(TxMode txMode)
TheYdb.Sdk.Services.Query.TxMode
is a YDB specific isolation level, you can read more about it here. -
BeginTransaction(IsolationLevel isolationLevel)
TheSystem.Data.IsolationLevel
parameter from the standard ADO.NET. The following isolation levels are supported:Serializable
andUnspecified
. Both are equivalent to theTxMode.SerializableRW
.
Calling BeginTransaction()
without parameters opens a transaction with level the TxMode.SerializableRW
.
Consider the following example of using a transaction:
await using var ydbConnection = new YdbConnection();
await ydbConnection.OpenAsync();
var ydbCommand = ydbConnection.CreateCommand();
ydbCommand.Transaction = ydbConnection.BeginTransaction();
ydbCommand.CommandText = """
UPSERT INTO episodes (series_id, season_id, episode_id, title, air_date)
VALUES (2, 5, 13, "Test Episode", Date("2018-08-27"))
""";
await ydbCommand.ExecuteNonQueryAsync();
ydbCommand.CommandText = """
INSERT INTO episodes(series_id, season_id, episode_id, title, air_date)
VALUES
(2, 5, 21, "Test 21", Date("2018-08-27")),
(2, 5, 22, "Test 22", Date("2018-08-27"))
""";
await ydbCommand.ExecuteNonQueryAsync();
await ydbCommand.Transaction.CommitAsync();
Here, a transaction with the Serializable
isolation level is opened, and two inserts into the episodes
table are executed.
Using parameters
SQL query parameters can be set using the YdbParameter
class.
In this example, the parameters $series_id
, $season_id
, and $limit_size
are declared within the SQL query and then added to the command using YdbParameter
objects.
await using var connection = new YdbConnection(_cmdOptions.SimpleConnectionString);
await connection.OpenAsync();
var ydbCommand = connection.CreateCommand();
ydbCommand.CommandText = """
DECLARE $series_id AS Uint64;
DECLARE $season_id AS Uint64;
DECLARE $limit_size AS Uint64;
SELECT series_id, season_id, episode_id, air_date, title
FROM episodes WHERE series_id = $series_id AND season_id > $season_id
ORDER BY $series_id, $season_id, $episode_id
LIMIT $limit_size;
""";
ydbCommand.Parameters.Add(new YdbParameter("$series_id", DbType.UInt64, 1U));
ydbCommand.Parameters.Add(new YdbParameter("$season_id", DbType.UInt64, 1U));
ydbCommand.Parameters.Add(new YdbParameter("$limit_size", DbType.UInt64, 3U));
var ydbDataReader = await ydbCommand.ExecuteReaderAsync();
_logger.LogInformation("Selected rows:");
while (await ydbDataReader.ReadAsync())
{
_logger.LogInformation(
"series_id: {series_id}, season_id: {season_id}, episode_id: {episode_id}, air_date: {air_date}, title: {title}",
ydbDataReader.GetUint64(0), ydbDataReader.GetUint64(1), ydbDataReader.GetUint64(2),
ydbDataReader.GetDateTime(3), ydbDataReader.GetString(4));
}
In this example, the parameters series_id
, season_id
, and limit_size
are declared within the SQL query and then added to the command using YdbParameter
objects.
@
prefix
Alternative style with Parameters can also be specified using the @
prefix. In this case, there is no need to declare variables within the query itself. The query will look like this:
ydbCommand.CommandText = """
SELECT series_id, season_id, episode_id, air_date, title
FROM episodes
WHERE series_id = @series_id AND season_id > @season_id
ORDER BY series_id, season_id, episode_id
LIMIT @limit_size;
""";
ydbCommand.Parameters.Add(new YdbParameter("series_id", DbType.UInt64, 1U));
ydbCommand.Parameters.Add(new YdbParameter("season_id", DbType.UInt64, 1U));
ydbCommand.Parameters.Add(new YdbParameter("limit_size", DbType.UInt64, 3U));
ADO.NET the query will be prepared for you so that the variables match YQL. The type will be determined according to the DbType or the .NET type of the value itself.
Type mapping table for reading
The following shows the mappings used when reading values.
The return type when using YdbCommand.ExecuteScalarAsync()
, YdbDataReader.GetValue()
and similar methods.
YDB type | .NET type |
---|---|
Bool |
bool |
Text (synonym Utf8 ) |
string |
Bytes (synonym String ) |
byte[] |
Uint8 |
byte |
Uint16 |
ushort |
Uint32 |
uint |
Uint64 |
ulong |
Int8 |
sbyte |
Int16 |
short |
Int32 |
int |
Int64 |
long |
Float |
float |
Double |
double |
Date |
DateTime |
Datetime |
DateTime |
Timestamp |
DateTime |
Decimal(22,9) |
Decimal |
Json |
string |
JsonDocument |
string |
Yson |
byte[] |
Type mapping table for writing
YDB type | DbType | .NET type |
---|---|---|
Bool |
Boolean |
bool |
Text (synonym Utf8 ) |
String , AnsiString , AnsiStringFixedLength , StringFixedLength |
string |
Bytes (synonym String ) |
Binary |
byte[] |
Uint8 |
Byte |
byte |
Uint16 |
UInt16 |
ushort |
Uint32 |
UInt32 |
uint |
Uint64 |
UInt64 |
ulong |
Int8 |
SByte |
sbyte |
Int16 |
Int16 |
short |
Int32 |
Int32 |
int |
Int64 |
Int64 |
long |
Float |
Single |
float |
Double |
Double |
double |
Date |
Date |
DateTime |
Datetime |
DateTime |
DateTime |
Timestamp |
DateTime2 (for .NET type DateTime ), DateTimeOffset (for .NET type DateTimeOffset ) |
DateTime , DateTimeOffset |
Decimal(22,9) |
Decimal , Currency |
decimal |
It's important to understand that if the DbType
is not specified, the parameter will be inferred from the System.Type
.
You can also specify any YDB type using the constructors from Ydb.Sdk.Value.YdbValue
. For example:
var parameter = new YdbParameter("$parameter", YdbValue.MakeJsonDocument("{\"type\": \"jsondoc\"}"));
Error handling
All exceptions related to database operations are subclasses of YdbException
.
To safely handle errors that might occur during command execution, you can use a try-catch
block. Here is an example:
try
{
await command.ExecuteNonQueryAsync();
}
catch (YdbException e)
{
Console.WriteLine($"Error executing command: {e}");
}
YdbException
Properties of The YdbException
exception has the following properties, which can help you handle errors properly:
-
IsTransient
returnstrue
if the error is temporary and can be resolved by retrying. For example, this might occur in cases of a transaction lock violation when the transaction fails to complete its commit. -
IsTransientWhenIdempotent
returnstrue
if the error is temporary and can be resolved by retrying the operation, provided that the database operation is idempotent. -
StatusCode
contains the database error code, which is helpful for logging and detailed analysis of the issue.
Warning
Please note that ADO.NET does not automatically retry failed operations, and you must implement retry logic in your code.
Integration of YDB and Dapper
To get started, you need an additional dependency Dapper.
Let's consider a complete example:
using Dapper;
using Ydb.Sdk.Ado;
await using var connection = await new YdbDataSource().OpenConnectionAsync();
await connection.ExecuteAsync("""
CREATE TABLE Users(
Id Int32,
Name Text,
Email Text,
PRIMARY KEY (Id)
);
""");
await connection.ExecuteAsync("INSERT INTO Users(Id, Name, Email) VALUES (@Id, @Name, @Email)",
new User { Id = 1, Name = "Name", Email = "Email" });
Console.WriteLine(await connection.QuerySingleAsync<User>("SELECT * FROM Users WHERE Id = @Id", new { Id = 1 }));
await connection.ExecuteAsync("DROP TABLE Users");
internal class User
{
public int Id { get; init; }
public string Name { get; init; } = null!;
public string Email { get; init; } = null!;
public override string ToString()
{
return $"Id: {Id}, Name: {Name}, Email: {Email}";
}
}
For more information, refer to the official documentation.
Important aspects
For Dapper to interpret DateTime
values as the YDB type DateTime
, execute the following code:
SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime);
By default, DateTime
is interpreted as Timestamp
.