diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index c9a7da0a..429e3aab 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -140,3 +140,8 @@ jobs:
dotnet ef migrations add InitialCreate
dotnet ef database update
dotnet run
+ - name: Run Linq2db.QuickStart
+ run: |
+ cd ./examples/Linq2db.QuickStart
+ dotnet run
+
diff --git a/examples/Linq2db.QuickStart/Linq2db.QuickStart.csproj b/examples/Linq2db.QuickStart/Linq2db.QuickStart.csproj
new file mode 100644
index 00000000..58619eb3
--- /dev/null
+++ b/examples/Linq2db.QuickStart/Linq2db.QuickStart.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/Linq2db.QuickStart/Program.cs b/examples/Linq2db.QuickStart/Program.cs
new file mode 100644
index 00000000..0b25df82
--- /dev/null
+++ b/examples/Linq2db.QuickStart/Program.cs
@@ -0,0 +1,483 @@
+using Linq2db.Ydb;
+using LinqToDB;
+using LinqToDB.Async;
+using LinqToDB.Data;
+using LinqToDB.Mapping;
+using Microsoft.Extensions.Logging;
+using Polly;
+
+namespace Linq2db.QuickStart;
+
+internal static class Program
+{
+ public static async Task Main()
+ {
+ using var factory = LoggerFactory.Create(b => b.AddConsole());
+ var app = new AppContext(factory.CreateLogger());
+ await app.Run();
+ }
+}
+
+#region LINQ2DB MODELS
+
+[Table("series")]
+public sealed class Series
+{
+ [PrimaryKey] [Column("series_id")] public ulong SeriesId { get; init; }
+
+ [Column("title")] [NotNull] public string Title { get; init; } = null!;
+
+ [Column("series_info")] public string? SeriesInfo { get; init; }
+
+ [Column("release_date")]
+ [DataType(DataType.Date)]
+ public DateTime ReleaseDate { get; init; }
+}
+
+[Table("seasons")]
+public sealed class Season
+{
+ [PrimaryKey] [Column("series_id")] public ulong SeriesId { get; init; }
+
+ [PrimaryKey] [Column("season_id")] public ulong SeasonId { get; init; }
+
+ [Column("title")] [NotNull] public string Title { get; init; } = null!;
+
+ [Column("first_aired")]
+ [DataType(DataType.Date)]
+ public DateTime FirstAired { get; init; }
+
+ [Column("last_aired")]
+ [DataType(DataType.Date)]
+ public DateTime LastAired { get; init; }
+}
+
+[Table("episodes")]
+public sealed class Episode
+{
+ [PrimaryKey] [Column("series_id")] public ulong SeriesId { get; init; }
+
+ [PrimaryKey] [Column("season_id")] public ulong SeasonId { get; init; }
+
+ [PrimaryKey] [Column("episode_id")] public ulong EpisodeId { get; init; }
+
+ [Column("title")] [NotNull] public string Title { get; init; } = null!;
+
+ [Column("air_date")]
+ [DataType(DataType.Date)]
+ public DateTime AirDate { get; init; }
+}
+
+#endregion
+
+#region LINQ2DB DATACONTEXT
+
+internal sealed class MyYdb(DataOptions options) : DataConnection(options)
+{
+ public ITable Series => this.GetTable();
+ public ITable Seasons => this.GetTable();
+ public ITable Episodes => this.GetTable();
+}
+
+#endregion
+
+#region SETTINGS
+
+internal sealed record Settings(
+ string Host,
+ int Port,
+ string Database,
+ bool UseTls,
+ int TlsPort)
+{
+ public string SimpleConnectionString =>
+ $"Host={Host};Port={(UseTls ? TlsPort : Port)};Database={Database};UseTls={(UseTls ? "true" : "false")}";
+}
+
+internal static class SettingsLoader
+{
+ public static Settings Load()
+ {
+ var host = Environment.GetEnvironmentVariable("YDB_HOST") ?? "localhost";
+ var port = TryInt(Environment.GetEnvironmentVariable("YDB_PORT"), 2136);
+ var db = Environment.GetEnvironmentVariable("YDB_DB") ?? "/local";
+ var useTls = TryBool(Environment.GetEnvironmentVariable("YDB_USE_TLS"), false);
+ var tls = TryInt(Environment.GetEnvironmentVariable("YDB_TLS_PORT"), 2135);
+
+ return new Settings(host, port, db, useTls, tls);
+
+ static int TryInt(string? s, int d)
+ {
+ return int.TryParse(s, out var v) ? v : d;
+ }
+
+ static bool TryBool(string? s, bool d)
+ {
+ return bool.TryParse(s, out var v) ? v : d;
+ }
+ }
+}
+
+#endregion
+
+internal class AppContext(ILogger logger)
+{
+ private readonly Settings _settings = SettingsLoader.Load();
+
+ private DataOptions BuildOptions(string? overrideConnectionString = null)
+ {
+ var cs = overrideConnectionString ?? _settings.SimpleConnectionString;
+ return new DataOptions().UseConnectionString("YDB", cs);
+ }
+
+ public async Task Run()
+ {
+ logger.LogInformation("Start app example");
+
+ await InitTables();
+ await LoadData();
+ await SelectWithParameters();
+ await RetryPolicy();
+
+ await InteractiveTransaction();
+ await TlsConnectionExample();
+ await ConnectionWithLoggerFactory();
+
+ logger.LogInformation("Finish app example");
+ }
+
+ private async Task InitTables()
+ {
+ DataConnection.AddProviderDetector(YdbTools.ProviderDetector);
+ await using var db = new MyYdb(BuildOptions());
+ try
+ {
+ await db.CreateTableAsync();
+ }
+ catch
+ {
+ logger.LogDebug("series exists");
+ }
+
+ try
+ {
+ await db.CreateTableAsync();
+ }
+ catch
+ {
+ logger.LogDebug("seasons exists");
+ }
+
+ try
+ {
+ await db.CreateTableAsync();
+ }
+ catch
+ {
+ logger.LogDebug("episodes exists");
+ }
+
+ logger.LogInformation("Created tables");
+ }
+
+ private async Task LoadData()
+ {
+ await using var db = new MyYdb(BuildOptions());
+
+ var series = new[]
+ {
+ new Series
+ {
+ SeriesId = 1, Title = "IT Crowd", ReleaseDate = new DateTime(2006, 02, 03),
+ SeriesInfo = "British sitcom..."
+ },
+ new Series
+ {
+ SeriesId = 2, Title = "Silicon Valley", ReleaseDate = new DateTime(2014, 04, 06),
+ SeriesInfo = "American comedy..."
+ }
+ };
+ foreach (var s in series) await db.InsertAsync(s);
+
+ var seasons = new List
+ {
+ new()
+ {
+ SeriesId = 1, SeasonId = 1, Title = "Season 1", FirstAired = new DateTime(2006, 02, 03),
+ LastAired = new DateTime(2006, 03, 03)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 2, Title = "Season 2", FirstAired = new DateTime(2007, 08, 24),
+ LastAired = new DateTime(2007, 09, 28)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 3, Title = "Season 3", FirstAired = new DateTime(2008, 11, 21),
+ LastAired = new DateTime(2008, 12, 26)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 4, Title = "Season 4", FirstAired = new DateTime(2010, 06, 25),
+ LastAired = new DateTime(2010, 07, 30)
+ },
+ new()
+ {
+ SeriesId = 2, SeasonId = 1, Title = "Season 1", FirstAired = new DateTime(2014, 04, 06),
+ LastAired = new DateTime(2014, 06, 01)
+ },
+ new()
+ {
+ SeriesId = 2, SeasonId = 2, Title = "Season 2", FirstAired = new DateTime(2015, 04, 12),
+ LastAired = new DateTime(2015, 06, 14)
+ },
+ new()
+ {
+ SeriesId = 2, SeasonId = 3, Title = "Season 3", FirstAired = new DateTime(2016, 04, 24),
+ LastAired = new DateTime(2016, 06, 26)
+ },
+ new()
+ {
+ SeriesId = 2, SeasonId = 4, Title = "Season 4", FirstAired = new DateTime(2017, 04, 23),
+ LastAired = new DateTime(2017, 06, 25)
+ },
+ new()
+ {
+ SeriesId = 2, SeasonId = 5, Title = "Season 5", FirstAired = new DateTime(2018, 03, 25),
+ LastAired = new DateTime(2018, 05, 13)
+ }
+ };
+ await db.BulkCopyAsync(seasons);
+
+ var eps = new List
+ {
+ new()
+ {
+ SeriesId = 1, SeasonId = 1, EpisodeId = 1, Title = "Yesterday's Jam",
+ AirDate = new DateTime(2006, 02, 03)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 1, EpisodeId = 2, Title = "Calamity Jen", AirDate = new DateTime(2006, 02, 03)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 1, EpisodeId = 3, Title = "Fifty-Fifty", AirDate = new DateTime(2006, 02, 10)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 1, EpisodeId = 4, Title = "The Red Door", AirDate = new DateTime(2006, 02, 17)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 2, EpisodeId = 1, Title = "The Work Outing",
+ AirDate = new DateTime(2007, 08, 24)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 2, EpisodeId = 2, Title = "Return of the Golden Child",
+ AirDate = new DateTime(2007, 08, 31)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 3, EpisodeId = 1, Title = "From Hell", AirDate = new DateTime(2008, 11, 21)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 3, EpisodeId = 2, Title = "Are We Not Men?",
+ AirDate = new DateTime(2008, 11, 28)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 4, EpisodeId = 1, Title = "Jen The Fredo", AirDate = new DateTime(2010, 06, 25)
+ },
+ new()
+ {
+ SeriesId = 1, SeasonId = 4, EpisodeId = 2, Title = "The Final Countdown",
+ AirDate = new DateTime(2010, 07, 02)
+ },
+ new()
+ {
+ SeriesId = 2, SeasonId = 2, EpisodeId = 1, Title = "Minimum Viable Product",
+ AirDate = new DateTime(2014, 04, 06)
+ },
+ new()
+ {
+ SeriesId = 2, SeasonId = 2, EpisodeId = 2, Title = "The Cap Table", AirDate = new DateTime(2014, 04, 13)
+ },
+ new()
+ {
+ SeriesId = 2, SeasonId = 1, EpisodeId = 3, Title = "Articles of Incorporation",
+ AirDate = new DateTime(2014, 04, 20)
+ },
+ new()
+ {
+ SeriesId = 2, SeasonId = 1, EpisodeId = 4, Title = "Fiduciary Duties",
+ AirDate = new DateTime(2014, 04, 27)
+ }
+ };
+ await db.BulkCopyAsync(eps);
+
+ _ = series[0].ReleaseDate.Ticks + (series[0].SeriesInfo?.Length ?? 0);
+ _ = seasons[0].FirstAired.Ticks + seasons[0].LastAired.Ticks;
+
+ logger.LogInformation("Loaded data");
+ }
+
+ private async Task SelectWithParameters()
+ {
+ await using var db = new MyYdb(BuildOptions());
+ ulong seriesId = 1;
+ ulong seasonId = 1;
+ ulong limit = 3;
+
+ var rows = await db.Episodes
+ .Where(e => e.SeriesId == seriesId && e.SeasonId > seasonId)
+ .OrderBy(e => e.SeriesId)
+ .ThenBy(e => e.SeasonId)
+ .ThenBy(e => e.EpisodeId)
+ .Take((int)limit)
+ .Select(e => new { e.SeriesId, e.SeasonId, e.EpisodeId, e.AirDate, e.Title })
+ .ToListAsync();
+
+ logger.LogInformation("Selected rows:");
+ foreach (var r in rows)
+ logger.LogInformation(
+ "series_id: {series_id}, season_id: {season_id}, episode_id: {episode_id}, air_date: {air_date}, title: {title}",
+ r.SeriesId, r.SeasonId, r.EpisodeId, r.AirDate, r.Title);
+ }
+
+ private async Task RetryPolicy()
+ {
+ var policy = Policy
+ .Handle()
+ .WaitAndRetryAsync(10, _ => TimeSpan.FromSeconds(1));
+
+ await policy.ExecuteAsync(async () =>
+ {
+ await using var db = new MyYdb(BuildOptions());
+
+ var statsRaw = await db.Episodes
+ .GroupBy(e => new { e.SeriesId, e.SeasonId })
+ .Select(g => new { g.Key.SeriesId, g.Key.SeasonId, Cnt = g.Count() })
+ .ToListAsync();
+
+ var stats = statsRaw
+ .OrderBy(x => x.SeriesId)
+ .ThenBy(x => x.SeasonId);
+
+ foreach (var x in stats)
+ logger.LogInformation("series_id: {series_id}, season_id: {season_id}, cnt: {cnt}",
+ x.SeriesId, x.SeasonId, x.Cnt);
+ });
+ }
+
+ private async Task InteractiveTransaction()
+ {
+ await using var db = new MyYdb(BuildOptions());
+ await using var tr = await db.BeginTransactionAsync();
+
+ await db.InsertAsync(new Episode
+ {
+ SeriesId = 2, SeasonId = 5, EpisodeId = 13, Title = "Test Episode", AirDate = new DateTime(2018, 08, 27)
+ });
+ await db.InsertAsync(new Episode
+ { SeriesId = 2, SeasonId = 5, EpisodeId = 21, Title = "Test 21", AirDate = new DateTime(2018, 08, 27) });
+ await db.InsertAsync(new Episode
+ { SeriesId = 2, SeasonId = 5, EpisodeId = 22, Title = "Test 22", AirDate = new DateTime(2018, 08, 27) });
+
+ await tr.CommitAsync();
+ logger.LogInformation("Commit transaction");
+
+ var title21 = await db.Episodes.Where(e => e.SeriesId == 2 && e.SeasonId == 5 && e.EpisodeId == 21)
+ .Select(e => e.Title).SingleAsync();
+ logger.LogInformation("New episode title: {title}", title21);
+
+ var title22 = await db.Episodes.Where(e => e.SeriesId == 2 && e.SeasonId == 5 && e.EpisodeId == 22)
+ .Select(e => e.Title).SingleAsync();
+ logger.LogInformation("New episode title: {title}", title22);
+
+ var title13 = await db.Episodes.Where(e => e.SeriesId == 2 && e.SeasonId == 5 && e.EpisodeId == 13)
+ .Select(e => e.Title).SingleAsync();
+ logger.LogInformation("Updated episode title: {title}", title13);
+ }
+
+ private async Task TlsConnectionExample()
+ {
+ if (!_settings.UseTls)
+ {
+ logger.LogInformation("Tls example was ignored");
+ return;
+ }
+
+ var caPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "ca.pem");
+ var tlsCs = $"Host={_settings.Host};Port={_settings.TlsPort};RootCertificate={caPath}";
+ await using var db = new MyYdb(BuildOptions(tlsCs));
+
+ var rows = await db.Seasons
+ .Where(sa => sa.SeriesId == 1)
+ .Join(db.Series, sa => sa.SeriesId, sr => sr.SeriesId,
+ (sa, sr) => new { SeasonTitle = sa.Title, SeriesTitle = sr.Title, sr.SeriesId, sa.SeasonId })
+ .OrderBy(x => x.SeriesId)
+ .ThenBy(x => x.SeasonId)
+ .ToListAsync();
+
+ foreach (var r in rows)
+ logger.LogInformation(
+ "season_title: {SeasonTitle}, series_title: {SeriesTitle}, series_id: {SeriesId}, season_id: {SeasonId}",
+ r.SeasonTitle, r.SeriesTitle, r.SeriesId, r.SeasonId);
+ }
+
+ private async Task ConnectionWithLoggerFactory()
+ {
+ var options = BuildOptions($"Host={_settings.Host};Port={_settings.Port}")
+ .UseTracing(ti =>
+ {
+ switch (ti.TraceInfoStep)
+ {
+ case TraceInfoStep.BeforeExecute:
+ logger.LogInformation("BeforeExecute: {sql}", ti.SqlText);
+ break;
+ case TraceInfoStep.AfterExecute:
+ logger.LogInformation("AfterExecute: {time} {records} recs", ti.ExecutionTime,
+ ti.RecordsAffected);
+ break;
+ case TraceInfoStep.Error:
+ logger.LogError(ti.Exception, "SQL error");
+ break;
+ }
+ });
+
+ await using var db = new MyYdb(options);
+
+ logger.LogInformation("Dropping tables of examples");
+ try
+ {
+ await db.DropTableAsync();
+ }
+ catch
+ {
+ /* ignored */
+ }
+
+ try
+ {
+ await db.DropTableAsync();
+ }
+ catch
+ {
+ /* ignored */
+ }
+
+ try
+ {
+ await db.DropTableAsync();
+ }
+ catch
+ {
+ /* ignored */
+ }
+
+ logger.LogInformation("Dropped tables of examples");
+ }
+}
\ No newline at end of file
diff --git a/examples/Linq2db.QuickStart/README.md b/examples/Linq2db.QuickStart/README.md
new file mode 100644
index 00000000..df510fe7
--- /dev/null
+++ b/examples/Linq2db.QuickStart/README.md
@@ -0,0 +1,39 @@
+# Linq2DB YDB Quick Start
+
+A tiny sample that shows how to connect to **YDB** with **Linq2DB**, create tables, seed demo data, run parameterized queries and a transaction.
+
+## Running QuickStart
+
+1. **Start YDB Local**
+ Follow the official guide: https://ydb.tech/docs/en/reference/docker/start
+ Defaults expected by the sample:
+ - non‑TLS port: **2136**
+ - TLS port: **2135**
+ - database: **/local**
+
+2. **(Optional) Configure environment variables**
+ The app reads connection settings from env vars (safe defaults are used if missing).
+
+ **Bash**
+ ```bash
+ export YDB_HOST=localhost
+ export YDB_PORT=2136
+ export YDB_DB=/local
+ export YDB_USE_TLS=false
+ # export YDB_TLS_PORT=2135
+ ```
+
+ **PowerShell**
+ ```powershell
+ $env:YDB_HOST="localhost"
+ $env:YDB_PORT="2136"
+ $env:YDB_DB="/local"
+ $env:YDB_USE_TLS="false"
+ # $env:YDB_TLS_PORT="2135"
+ ```
+
+3. **Restore & run**
+ ```bash
+ dotnet restore
+ dotnet run
+ ```
\ No newline at end of file
diff --git a/examples/YdbExamples.sln b/examples/YdbExamples.sln
index 31c103ee..ecf70e2a 100644
--- a/examples/YdbExamples.sln
+++ b/examples/YdbExamples.sln
@@ -27,6 +27,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Database.Operations.Tutoria
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ydb.Sdk.AdoNet.Yandex.Cloud.Serverless.Container", "Ydb.Sdk.AdoNet.Yandex.Cloud.Serverless.Container\Ydb.Sdk.AdoNet.Yandex.Cloud.Serverless.Container.csproj", "{77625697-498B-4879-BABA-046EE93E7AF7}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Linq2db.QuickStart", "Linq2db.QuickStart\Linq2db.QuickStart.csproj", "{2D3A5D18-9005-4E19-92C0-002069CAC7BB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -81,6 +83,10 @@ Global
{77625697-498B-4879-BABA-046EE93E7AF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77625697-498B-4879-BABA-046EE93E7AF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77625697-498B-4879-BABA-046EE93E7AF7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2D3A5D18-9005-4E19-92C0-002069CAC7BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D3A5D18-9005-4E19-92C0-002069CAC7BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D3A5D18-9005-4E19-92C0-002069CAC7BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D3A5D18-9005-4E19-92C0-002069CAC7BB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE