Провайдер YDB для LinqToDB
- Введение
- Установка провайдера YDB
- Конфигурация провайдера
- Использование
- Таблица соответствия .NET типов с типами YDB
- Пример кастомного Decimal
- Пример кастомного DbType
- Генерация схемы из атрибутов
- Эволюция схемы (добавление поля)
- Индексы YDB: как задать параметры
- Связи между сущностями
- Примеры сгенерированного YQL для выборок по связям
- Пример «прикладной» сущности и генерируемого DDL
- Массовые операции: вставка, обновление и удаление
Введение
Это руководство по использованию LinqToDB с YDB.
linq2db — лёгкий и быстрый ORM для .NET, предоставляющий типобезопасные LINQ запросы и точный контроль над SQL. Провайдер YDB формирует корректный YQL, поддерживает типы YDB, генерацию схемы и массовые операции (Bulk Copy).
Установка провайдера YDB
Примеры добавления зависимостей:
dotnet add package Community.Ydb.Linq2db
dotnet add package linq2db
<ItemGroup>
<PackageReference Include="Community.Ydb.Linq2db" Version="$(CommunityYdbLinqToDbVersion)" />
<PackageReference Include="linq2db" Version="$(LinqToDbVersion)" />
</ItemGroup>
Конфигурация провайдера
Настройте LinqToDB на использование YDB через код:
DataConnection.AddProviderDetector(YdbTools.ProviderDetector);
// Вариант 1: локальный YDB через строку подключения
//
// Пример: локальный YDB, работающий на localhost:2136 с базой "/local"
using var localDb = new DataConnection(
new DataOptions().UseConnectionString(
"YDB",
"Host=localhost;Port=2136;Database=/local;UseTls=false"
)
);
// Вариант 2: YDB в облаке только через YdbConnectionStringBuilder
//
// Пример: подключение, собранное из явных значений host/port/database.
static async Task<DataConnection> BuildYdbDataConnection()
{
var ydbConnectionBuilder = new YdbConnectionStringBuilder
{
Host = "server",
Port = 2135,
Database = "/ru-prestable/my-table",
UseTls = true
};
await using var ydbConnection = new YdbConnection(ydbConnectionBuilder);
return YdbTools.CreateDataConnection(ydbConnection);
}
Использование
Используйте провайдер как и любой другой провайдер Linq To DB: сопоставляйте классы сущностей с таблицами и выполняйте запросы через DataConnection/ITable<T>. Ниже — типовая таблица соответствий и примеры генерации схемы из атрибутов.
Таблица соответствия .NET типов с типами YDB
| .NET тип(ы) | Linq To DB DataType |
Тип в YDB | Примечание |
|---|---|---|---|
bool |
Boolean |
Bool |
— |
string |
NVarChar / VarChar / Char / NChar |
Utf8 |
UTF-8 строка. Text и Utf8 — одно и то же; что выводить, решает генератор DDL |
byte[] |
VarBinary / Binary / Blob |
String |
Бинарные данные. Bytes и String в YDB равнозначны |
Guid |
Guid |
Uuid |
128-битный UUID (RFC 4122). Проверка версии (v1/v4/v7) не выполняется — нужную версию генерируйте в приложении |
DateOnly / DateTime |
Date |
Date |
Хранится как дата в UTC без времени. Диапазон в YDB 1970-01-01..2106-01-01. Диапазон .NET шире (0001..9999), значения вне YDB записать нельзя. |
DateTime |
DateTime |
Datetime |
Момент времени с точностью до секунды в UTC. DateTime.Kind не сохраняется; рекомендуется передавать UTC. Диапазон YDB 1970-01-01..2106-01-01 |
DateTime / DateTimeOffset |
DateTime2 |
Timestamp |
Момент времени с микросекундной точностью. При записи DateTimeOffset смещение отбрасывается, значение конвертируется в UTC. Диапазон ограничен 1970-01-01..2106-01-01; при попытке записи все диапазона будет ошибка |
TimeSpan |
Interval |
Interval |
Интервал с микросекундной точностью. Диапазон YDB меньше диапазона .NET TimeSpan; значения вне диапазона не поддерживаются. Доли микросекунды отбрасываются (округление вниз). |
decimal |
Decimal |
Decimal(p,s) |
По умолчанию — Decimal(22,9). Чтобы задать свои Precision/Scale, используйте Decimal(p,s). Пример |
float |
Single |
Float |
— |
double |
Double |
Double |
— |
sbyte / byte |
SByte / Byte |
Int8 / Uint8 |
— |
short / ushort |
Int16 / UInt16 |
Int16 / Uint16 |
— |
int / uint |
Int32 / UInt32 |
Int32 / Uint32 |
— |
long / ulong |
Int64 / UInt64 |
Int64 / Uint64 |
— |
string |
Json |
Json |
Текстовый JSON. |
byte[] |
BinaryJson |
JsonDocument |
Бинарный JSON. |
DateOnly / DateTime |
Date |
Date32 |
Более широкий диапазон дат. Укажите DbType = "Date32". Пример |
DateTime |
DateTime |
Datetime64 |
Точность до секунд, расширенный диапазон. Укажите DbType = "Datetime64". Пример |
DateTime / DateTimeOffset |
DateTime2 |
Timestamp64 |
Точность до микросекунд, расширенный диапазон. Укажите DbType = "Timestamp64". Пример |
TimeSpan |
Interval |
Interval64 |
Более широкий диапазон интервалов. Укажите DbType = "Interval64". Пример |
Совет
Точное Precision/Scale задаётся атрибутами: [Column(DataType = DataType.Decimal, Precision = 22, Scale = 9)].
Примечание
По умолчанию (если на колонке не задан DbType) применяются базовые типы YDB: Date, Datetime, Timestamp, Interval.
Чтобы точечно включить расширенные типы, укажи DbType, например: [Column(DbType = "Date32")]. Обе семьи типов могут сосуществовать в одной таблице.
Пример кастомного Decimal
[Table("amounts")]
public sealed class AmountRow
{
[PrimaryKey] public long Id { get; set; }
// Custom precision & scale: Decimal(25,10)
[Column("amount", DataType = DataType.Decimal, Precision = 25, Scale = 10), NotNull]
public decimal Amount { get; set; }
}
Пример кастомного DbType
[Table("events")]
public sealed class EventRow
{
[PrimaryKey] public long Id { get; set; }
// Extended-range timestamp
[Column("happened_at", DbType = "Timestamp64"), NotNull]
public DateTime HappenedAt { get; set; }
// Extended-range date
[Column("due_on", DbType = "Date32"), NotNull]
public DateTime DueOn { get; set; }
// Second precision date
[Column("made_at", DbType = "Datetime64"), NotNull]
public DateTime MadeAt { get; set; }
// Extended-range interval
[Column("duration", DbType = "Interval64"), NotNull]
public TimeSpan Duration { get; set; }
}
Генерация схемы из атрибутов
Опишите сущность с помощью атрибутов Linq To DB; провайдер создаст таблицу и индексы.
using LinqToDB.Mapping;
[Table(Name = "Groups")]
[Index("GroupName", Name = "group_name_index")]
public class Group
{
[PrimaryKey, Column("GroupId")]
public int Id { get; set; }
[Column("GroupName")]
public string? Name { get; set; }
}
Сгенерированный DDL (YDB):
CREATE TABLE Groups (
GroupId Int32 NOT NULL,
GroupName Utf8,
PRIMARY KEY (GroupId)
);
ALTER TABLE Groups
ADD INDEX group_name_index GLOBAL
ON (GroupName);
Эволюция схемы (добавление поля)
Добавим поле Department к сущности Group:
[Column] public string? Department { get; set; }
Изменение схемы (DDL):
ALTER TABLE Groups
ADD COLUMN Department Utf8;
Примечание
LinqToDB не управляет миграциями. DDL ниже — иллюстративный; применяйте его через Liquibase/Flyway (рекомендуется). Для быстрых локальных изменений можно выполнить его напрямую через db.Execute(...) или YDB CLI.
using var db = new DataConnection("YDB", connectionString);
// Применяем изменение схемы (добавляем колонку):
db.Execute(@"
ALTER TABLE Groups
ADD COLUMN Department Utf8;
");
Индексы YDB: как задать параметры
Через атрибут [Index] вы задаёте имя, колонки и уникальность индекса. Провайдер создаёт вторичный индекс как GLOBAL.
Параметры ASYNC/SYNC и COVER(...) через атрибут не задаются — их добавляют отдельной DDL-командой после создания таблицы.
Вариант A — через атрибут (имя + Unique)
[Table(Name = "Groups", IsColumnAttributeRequired = false)]
[Index("GroupName", Name = "group_name_index", Unique = true)]
public class Group
{
[PrimaryKey, Column("GroupId")] public int Id { get; set; }
[Column("GroupName")] public string? Name { get; set; }
// Колонка, которую при желании можно добавить в COVER отдельной командой
[Column] public string? Department { get; set; }
}
// При db.CreateTable<Group>() будет создан GLOBAL UNIQUE индекс по GroupName.
Сгенерированный DDL будет эквивалентен
CREATE TABLE Groups (
GroupId Int32 NOT NULL,
GroupName Utf8,
Department Utf8,
PRIMARY KEY (GroupId)
);
ALTER TABLE Groups
ADD INDEX group_name_index GLOBAL UNIQUE
ON (GroupName);
Вариант B — расширенные параметры (ASYNC, COVER) отдельной командой
[Table(Name = "Groups", IsColumnAttributeRequired = false)]
public class Group
{
[PrimaryKey, Column("GroupId")] public int Id { get; set; }
[Column("GroupName")] public string? Name { get; set; }
[Column] public string? Department { get; set; }
}
public static class Demo
{
public static void Main()
{
using var db = new DataConnection("YDB", "Host=localhost;Port=2136;Database=/local;UseTls=false");
// 1) Создаём таблицу из атрибутов
db.CreateTable<Group>();
// 2) Добавляем индекс с параметрами ASYNC и COVER
db.Execute(@"
ALTER TABLE Groups
ADD INDEX group_name_index GLOBAL ASYNC
ON (GroupName)
COVER (Department);
");
}
}
Связи между сущностями
Опишите навигации через атрибуты [Association]. Пример «один‑ко‑многим» между Group и Student:
[Table("Students")]
public class Student
{
[PrimaryKey, Column("StudentId")]
public int Id { get; set; }
[Column("StudentName")]
public string Name { get; set; } = null!;
[Column]
public int GroupId { get; set; }
// многие-к-одному
[Association(ThisKey = nameof(GroupId), OtherKey = nameof(Group.Id), CanBeNull = false)]
public Group Group { get; set; } = null!;
}
[Table("Groups")]
public class Group
{
[PrimaryKey, Column("GroupId")]
public int Id { get; set; }
[Column("GroupName")]
public string? Name { get; set; }
// один-ко-многим
[Association(ThisKey = nameof(Id), OtherKey = nameof(Student.GroupId))]
public IEnumerable<Student> Students { get; set; } = null!;
}
Создаваемые таблицы:
CREATE TABLE Groups (
GroupId Int32 NOT NULL,
GroupName Utf8,
PRIMARY KEY (GroupId)
);
CREATE TABLE Students (
StudentId Int32 NOT NULL,
StudentName Utf8,
GroupId Int32,
PRIMARY KEY (StudentId)
);
Примеры сгенерированного YQL для выборок по связям
// 1) SELECT g.GroupId, g.GroupName FROM Groups AS g WHERE g.GroupName = 'M3439';
var grp = db.GetTable<Group>()
.Where(g => g.Name == "M3439")
.Select(g => new { g.Id, g.Name })
.FirstOrDefault();
// 2) SELECT s.StudentId, s.StudentName, s.GroupId FROM Students AS s WHERE s.GroupId = ?;
var students = grp == null
? new List<Student>()
: db.GetTable<Student>()
.Where(s => s.GroupId == grp.Id)
.Select(s => new { s.Id, s.Name, s.GroupId })
.ToList();
var joined =
(from g in db.GetTable<Group>()
join s in db.GetTable<Student>() on g.Id equals s.GroupId
where g.Name == "M3439"
select new { GroupId = g.Id, GroupName = g.Name, StudentId = s.Id, StudentName = s.Name, s.GroupId })
.ToList();
SELECT
g.GroupId,
g.GroupName
FROM
Groups AS g
WHERE
g.GroupName = 'M3439';
SELECT
s.StudentId,
s.StudentName,
s.GroupId
FROM
Students AS s
WHERE
s.GroupId = ?;
SELECT
g.GroupId,
g.GroupName,
s.StudentId,
s.StudentName,
s.GroupId
FROM
Groups AS g
JOIN
Students AS s
ON g.GroupId = s.GroupId
WHERE
g.GroupName = 'M3439';
Пример «прикладной» сущности и генерируемого DDL
Этот раздел показывает практическую сущность. Он демонстрирует:
- реалистичный набор колонок (ФИО, e-mail, дата найма, зарплата, флаги, целочисленные значения);
- точные типы (например, Decimal(22,9) для денежных значений, Date для дат, Utf8/Bool/Int32/Int64);
- GLOBAL вторичный индекс по full_name для быстрых поисков и сортировки;
- сквозной сценарий: маппинг → создание таблицы → CRUD с сгенерированным YQL/DDL.
При вызове db.CreateTable<Employee>() Linq To DB создаёт таблицу и применяет атрибут [Index] как GLOBAL-индекс YDB.
using System;
using LinqToDB.Mapping;
[Table("employee")]
[Index("full_name", Name = "employee_full_name_idx")]
public class Employee
{
[PrimaryKey] public long Id { get; set; }
[Column("full_name"), NotNull] public string FullName { get; set; } = null!;
[Column, NotNull] public string Email { get; set; } = null!;
[Column("hire_date", DataType = DataType.Date), NotNull]
public DateTime HireDate { get; set; }
[Column(DataType = DataType.Decimal, Precision = 22, Scale = 9), NotNull]
public decimal Salary { get; set; }
[Column("is_active"), NotNull] public bool IsActive { get; set; }
[Column, NotNull] public string Department { get; set; } = null!;
[Column, NotNull] public int Age { get; set; }
}
Сгенерированный DDL:
CREATE TABLE employee (
Id Int64 NOT NULL,
full_name Utf8 NOT NULL,
Email Utf8 NOT NULL,
hire_date Date NOT NULL,
Salary Decimal(22,9) NOT NULL,
is_active Bool NOT NULL,
Department Utf8 NOT NULL,
Age Int32 NOT NULL,
PRIMARY KEY (Id)
);
ALTER TABLE employee
ADD INDEX employee_full_name_idx GLOBAL
ON (full_name);
Пример использования:
using System;
using System.Linq;
using LinqToDB;
using LinqToDB.Data;
// Этот пример использует прямую строку подключения и предназначен для локального / тестового YDB.
// Если вам нужно подключиться к YDB в Yandex Cloud,
// используйте YdbConnectionStringBuilder и YdbConnection (см. раздел про конфигурацию провайдера).
using var db = new DataConnection(
new DataOptions().UseConnectionString(
"YDB",
"Host=localhost;Port=2136;Database=/local;UseTls=false"
)
);
// INSERT
var employee = new Employee
{
Id = 1L,
FullName = "Example",
Email = "example@example.com",
HireDate = new DateTime(2023, 12, 20),
Salary = 500000.000000000m,
IsActive = true,
Department = "YDB AppTeam",
Age = 23,
};
db.Insert(employee);
// SELECT по первичному ключу
var loaded = db.GetTable<Employee>()
.FirstOrDefault(e => e.Id == employee.Id);
// Обновим Email/Department/Salary у сотрудника с заданным Id
db.GetTable<Employee>()
.Where(e => e.Id == employee.Id)
.Set(e => e.Email, "example+updated@example.com")
.Set(e => e.Department, "Analytics")
.Set(e => e.Salary, 550000.000000000m)
.Update();
// DELETE по первичному ключу
db.GetTable<Employee>()
.Where(e => e.Id == employee.Id)
.Delete();
Примеры YQL, формируемые провайдером для простых операций:
-
Вставка одной записи
INSERT INTO employee (Age,Department,Email,full_name,hire_date,is_active,Salary,Id) VALUES (?,?,?,?,?,?,?,?); -
Чтение по первичному ключу
SELECT e.Id, e.full_name, e.Email, e.hire_date, e.Salary, e.is_active, e.Department, e.Age FROM employee AS e WHERE e.Id = ?; -
Обновление по первичному ключу
UPDATE employee SET Email = ?, Department = ?, Salary = ? WHERE Id = ?;
Примечание
Провайдер выводит параметры (?), потому что значения и типы привязываются драйвером, а не объявляются в тексте запроса. Когда YQL требует типизированные параметры, провайдер автоматически добавляет соответствующие DECLARE. Для “нестандартных” паттернов вроде upsert-записей используйте UPSERT с параметризацией — провайдер генерирует такие выражения? как обычный YQL с параметрами.
- Удаление по первичному ключу
DELETE FROM employee WHERE Id = ?;
Массовые операции: вставка, обновление и удаление
Массовая вставка (BulkCopy)
var now = DateTime.UtcNow;
var data = Enumerable.Range(0, 15_000).Select(i => new SimpleEntity
{
Id = i,
IntVal = i,
DecVal = 0m,
StrVal = $"Name {i}",
BoolVal = (i & 1) == 0,
DtVal = now,
});
Массовое обновление (WHERE IN)
var ids = Enumerable.Range(0, 15_000).ToArray();
table.Where(t => ids.Contains(t.Id))
.Set(_ => _.DecVal, 1.23m)
.Set(_ => _.StrVal, "updated")
.Set(_ => _.BoolVal, true)
.Update();
DECLARE $Gen_List_Primitive_1 AS List<Int32>;
UPDATE
SimpleEntity
SET
DecVal = Decimal('1.23', 22, 9),
StrVal = 'updated'u,
BoolVal = true
WHERE
SimpleEntity.Id IN $Gen_List_Primitive_1
Массовое удаление (WHERE IN)
table.Delete(t => ids.Contains(t.Id));
DECLARE $Gen_List_Primitive_1 AS List<Int32>;
DELETE FROM
SimpleEntity
WHERE
SimpleEntity.Id IN $Gen_List_Primitive_1