diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..524c6b4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,73 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Documentation site for all LinqToDB projects, hosted at [linq2db.github.io](https://linq2db.github.io). Built with a **custom DocFX binary** (`docfx/docfx`) — not the standard NuGet/dotnet tool version. The custom build adds `globalPrefix` support to handle namespace conflicts across EF Core versions (dotnet/docfx#8966). + +## Build Commands + +```bash +# Update submodules to latest release (required before first build) +./submodules.cmd # runs: git submodule update --remote --merge + +# Local build — generates static site in _site/ +./local.cmd # runs build.ps1 with deploy=$false + +# Manual build +powershell ./build.ps1 -deploy $false # build only +powershell ./build.ps1 -deploy $true # build + deploy (needs GITHUB_PAT env var) +``` + +Requires **.NET SDK 10.0** (see `global.json`). The build pre-compiles `LinqToDB.FSharp` as a DocFX workaround before running `./docfx/docfx source/docfx.json`. + +## Documentation Structure + +- **`source/index.md`** — Landing page with hero, interactive SQL demo, navigation cards +- **`source/documentation/`** — Structured guides (Markdown + `toc.yml` per section) + - `get-started/` — Installation and setup guides + - `general/` — Core concepts (connections, interceptors, metrics, database support) + - `sql/` — SQL features (bulk copy, CTEs, MERGE, joins, window functions) + - `how-to/` — Task-oriented guides + - `project/` — Contributing, issue reporting +- **`source/articles/`** — Blog/news content (release notes) +- **`source/api/`** — Auto-generated API docs (output of DocFX metadata extraction, not hand-edited) +- **`source/templates/custom/`** — CSS/JS overrides for DocFX modern template +- **`_site/`** — Generated static HTML output (gitignored) + +## Landing Page SQL Demo + +The landing page has a tabbed demo showing C# LINQ → generated SQL. See **`tools/sql-demo/README.md`** for full maintenance guide. Key points: + +- SQL is pre-generated using `ToSqlQuery()` from the linq2db library +- Test file: `tools/sql-demo/SqlDemoGenerator.cs` — copy to `linq2db/Tests/Tests.Playground/` to run +- Tabs are pure CSS (radio inputs + `:checked` selectors), no JavaScript +- Use ` ` instead of blank lines inside `
` blocks (DocFX wraps blank lines in `` tags) + +## Key Configuration Files + +- **`source/docfx.json`** — Main DocFX config: 17 metadata sources (API namespaces) + build settings +- **`source/toc.yml`** — Top-level navigation (Documentation, Articles, API) +- **`source/filter.yml`** — API filtering rules + +## Submodules + +Source code for API documentation lives in `submodules/`: +- **`linq2db`** (release branch) — Main ORM library, F# extensions, tools, scaffold, remote packages +- **`LinqToDB.Identity`** (master) — ASP.NET Identity provider +- **`linq2db.EntityFrameworkCore`** — EF Core 3/8/9/10 integration +- **`IdentityServer4.LinqToDB`** — IdentityServer4 integration + +Run `./submodules.cmd` after cloning or when API docs need updating. + +## CI/CD (Azure Pipelines) + +- **`azure-pipelines.yml`** — Triggers on master push: builds and deploys to linq2db.github.io +- **`azure-pipelines.build.yml`** — Triggers on PRs: build-only validation + +Both use Windows 2022 VM with .NET SDK 10.x. + +## Editor Conventions + +Per `.editorconfig`: tab indentation (size 4), CRLF line endings, final newline required. diff --git a/source/articles/index.md b/source/articles/index.md new file mode 100644 index 0000000..5e8236f --- /dev/null +++ b/source/articles/index.md @@ -0,0 +1,19 @@ +--- +title: Articles +_disableAffix: true +_disableNextArticle: true +--- + +# Articles + +
diff --git a/source/articles/toc.yml b/source/articles/toc.yml index 13dc032..b0c87c5 100644 --- a/source/articles/toc.yml +++ b/source/articles/toc.yml @@ -1,18 +1,2 @@ - name: Release Notes href: https://github.com/linq2db/linq2db/wiki/Releases-and-Roadmap -- name: Get Started - href: get-started/toc.yml -- name: General Topics - href: general/toc.yml -- name: SQL - href: sql/toc.yml -- name: How To - href: how-to/toc.yml -- name: CLI Scaffold Tool - href: CLI.md -- name: T4 Templates (Obsoleted) - href: T4.md -- name: FAQ - href: FAQ.md -- name: Project - href: project/toc.yml \ No newline at end of file diff --git a/source/docfx.json b/source/docfx.json index e95fb8f..cd5446e 100644 --- a/source/docfx.json +++ b/source/docfx.json @@ -220,6 +220,7 @@ "files": [ "api/**/*.{md,yml}", "articles/**/*.{md,yml}", + "documentation/**/*.{md,yml}", "toc.yml", "*.md" ] @@ -231,7 +232,7 @@ "images/**", "**.png", "**.jpg", - "articles/cli-help.txt", + "documentation/cli-help.txt", ".nojekyll" ] } @@ -247,7 +248,15 @@ "_appFaviconPath": "images/icon.ico", "_enableSearch": true, "_disableNewTab": false, - "_disableContribution": true + "_gitContribute": { + "repo": "https://github.com/linq2db/docs", + "branch": "master" + } + }, + "fileMetadata": { + "_disableContribution": { + "api/**/**.yml": true + } }, "fileMetadataFiles": [], "template": [ diff --git a/source/articles/CLI.md b/source/documentation/CLI.md similarity index 100% rename from source/articles/CLI.md rename to source/documentation/CLI.md diff --git a/source/articles/FAQ.md b/source/documentation/FAQ.md similarity index 100% rename from source/articles/FAQ.md rename to source/documentation/FAQ.md diff --git a/source/articles/T4.md b/source/documentation/T4.md similarity index 100% rename from source/articles/T4.md rename to source/documentation/T4.md diff --git a/source/articles/cli-help.txt b/source/documentation/cli-help.txt similarity index 100% rename from source/articles/cli-help.txt rename to source/documentation/cli-help.txt diff --git a/source/articles/general/Managing-data-connection.md b/source/documentation/general/Managing-data-connection.md similarity index 100% rename from source/articles/general/Managing-data-connection.md rename to source/documentation/general/Managing-data-connection.md diff --git a/source/articles/general/Video.md b/source/documentation/general/Video.md similarity index 100% rename from source/articles/general/Video.md rename to source/documentation/general/Video.md diff --git a/source/articles/general/databases.md b/source/documentation/general/databases.md similarity index 100% rename from source/articles/general/databases.md rename to source/documentation/general/databases.md diff --git a/source/articles/general/interceptors.md b/source/documentation/general/interceptors.md similarity index 100% rename from source/articles/general/interceptors.md rename to source/documentation/general/interceptors.md diff --git a/source/articles/general/metrics.md b/source/documentation/general/metrics.md similarity index 100% rename from source/articles/general/metrics.md rename to source/documentation/general/metrics.md diff --git a/source/articles/general/toc.yml b/source/documentation/general/toc.yml similarity index 100% rename from source/articles/general/toc.yml rename to source/documentation/general/toc.yml diff --git a/source/articles/get-started/asp-dotnet-core/index.md b/source/documentation/get-started/asp-dotnet-core/index.md similarity index 100% rename from source/articles/get-started/asp-dotnet-core/index.md rename to source/documentation/get-started/asp-dotnet-core/index.md diff --git a/source/articles/get-started/full-dotnet/existing-db.md b/source/documentation/get-started/full-dotnet/existing-db.md similarity index 100% rename from source/articles/get-started/full-dotnet/existing-db.md rename to source/documentation/get-started/full-dotnet/existing-db.md diff --git a/source/articles/get-started/full-dotnet/static/output-existing-db.png b/source/documentation/get-started/full-dotnet/static/output-existing-db.png similarity index 100% rename from source/articles/get-started/full-dotnet/static/output-existing-db.png rename to source/documentation/get-started/full-dotnet/static/output-existing-db.png diff --git a/source/articles/get-started/install/index.md b/source/documentation/get-started/install/index.md similarity index 100% rename from source/articles/get-started/install/index.md rename to source/documentation/get-started/install/index.md diff --git a/source/articles/get-started/toc.yml b/source/documentation/get-started/toc.yml similarity index 100% rename from source/articles/get-started/toc.yml rename to source/documentation/get-started/toc.yml diff --git a/source/articles/how-to/teach-linq2db-convert-custom-net-code-to-sql.md b/source/documentation/how-to/teach-linq2db-convert-custom-net-code-to-sql.md similarity index 100% rename from source/articles/how-to/teach-linq2db-convert-custom-net-code-to-sql.md rename to source/documentation/how-to/teach-linq2db-convert-custom-net-code-to-sql.md diff --git a/source/articles/how-to/toc.yml b/source/documentation/how-to/toc.yml similarity index 100% rename from source/articles/how-to/toc.yml rename to source/documentation/how-to/toc.yml diff --git a/source/articles/how-to/using-mapvalue-attribute-to-control-mapping.md b/source/documentation/how-to/using-mapvalue-attribute-to-control-mapping.md similarity index 100% rename from source/articles/how-to/using-mapvalue-attribute-to-control-mapping.md rename to source/documentation/how-to/using-mapvalue-attribute-to-control-mapping.md diff --git a/source/documentation/index.md b/source/documentation/index.md new file mode 100644 index 0000000..d11e85a --- /dev/null +++ b/source/documentation/index.md @@ -0,0 +1,69 @@ +--- +title: Documentation +_disableAffix: true +_disableNextArticle: true +--- + +# Linq To DB Documentation + ++diff --git a/source/articles/links.md b/source/documentation/links.md similarity index 100% rename from source/articles/links.md rename to source/documentation/links.md diff --git a/source/articles/project/Issue-reporting.md b/source/documentation/project/Issue-reporting.md similarity index 100% rename from source/articles/project/Issue-reporting.md rename to source/documentation/project/Issue-reporting.md diff --git a/source/articles/project/contrib.md b/source/documentation/project/contrib.md similarity index 100% rename from source/articles/project/contrib.md rename to source/documentation/project/contrib.md diff --git a/source/articles/project/toc.yml b/source/documentation/project/toc.yml similarity index 100% rename from source/articles/project/toc.yml rename to source/documentation/project/toc.yml diff --git a/source/articles/sql/Bulk-Copy.md b/source/documentation/sql/Bulk-Copy.md similarity index 100% rename from source/articles/sql/Bulk-Copy.md rename to source/documentation/sql/Bulk-Copy.md diff --git a/source/articles/sql/CTE.md b/source/documentation/sql/CTE.md similarity index 100% rename from source/articles/sql/CTE.md rename to source/documentation/sql/CTE.md diff --git a/source/articles/sql/Join-Operators.md b/source/documentation/sql/Join-Operators.md similarity index 100% rename from source/articles/sql/Join-Operators.md rename to source/documentation/sql/Join-Operators.md diff --git a/source/articles/sql/Query-Extensions.md b/source/documentation/sql/Query-Extensions.md similarity index 100% rename from source/articles/sql/Query-Extensions.md rename to source/documentation/sql/Query-Extensions.md diff --git a/source/articles/sql/Window-Functions-(Analytic-Functions).md b/source/documentation/sql/Window-Functions-(Analytic-Functions).md similarity index 100% rename from source/articles/sql/Window-Functions-(Analytic-Functions).md rename to source/documentation/sql/Window-Functions-(Analytic-Functions).md diff --git a/source/articles/sql/merge/Merge-API-Background.md b/source/documentation/sql/merge/Merge-API-Background.md similarity index 100% rename from source/articles/sql/merge/Merge-API-Background.md rename to source/documentation/sql/merge/Merge-API-Background.md diff --git a/source/articles/sql/merge/Merge-API-Description.md b/source/documentation/sql/merge/Merge-API-Description.md similarity index 100% rename from source/articles/sql/merge/Merge-API-Description.md rename to source/documentation/sql/merge/Merge-API-Description.md diff --git a/source/articles/sql/merge/Merge-API-Migration.md b/source/documentation/sql/merge/Merge-API-Migration.md similarity index 100% rename from source/articles/sql/merge/Merge-API-Migration.md rename to source/documentation/sql/merge/Merge-API-Migration.md diff --git a/source/articles/sql/merge/Merge-API.md b/source/documentation/sql/merge/Merge-API.md similarity index 100% rename from source/articles/sql/merge/Merge-API.md rename to source/documentation/sql/merge/Merge-API.md diff --git a/source/articles/sql/merge/toc.yml b/source/documentation/sql/merge/toc.yml similarity index 100% rename from source/articles/sql/merge/toc.yml rename to source/documentation/sql/merge/toc.yml diff --git a/source/articles/sql/toc.yml b/source/documentation/sql/toc.yml similarity index 100% rename from source/articles/sql/toc.yml rename to source/documentation/sql/toc.yml diff --git a/source/documentation/toc.yml b/source/documentation/toc.yml new file mode 100644 index 0000000..14e25c4 --- /dev/null +++ b/source/documentation/toc.yml @@ -0,0 +1,16 @@ +- name: Get Started + href: get-started/toc.yml +- name: General Topics + href: general/toc.yml +- name: SQL + href: sql/toc.yml +- name: How To + href: how-to/toc.yml +- name: CLI Scaffold Tool + href: CLI.md +- name: T4 Templates (Obsoleted) + href: T4.md +- name: FAQ + href: FAQ.md +- name: Project + href: project/toc.yml diff --git a/source/index.md b/source/index.md index b1d297d..598c1fd 100644 --- a/source/index.md +++ b/source/index.md @@ -1 +1,434 @@ -[!include[intro](../submodules/linq2db/readme.md)] +--- +title: Linq To DB +_disableToc: true +_disableAffix: true +_disableBreadcrumb: true +_disableNextArticle: true +_disableContribution: true +--- + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + ++ + ++++++++ .NET Foundation Member ++Data access
+
made simpleThe fastest LINQ database access library. A lightweight, type-safe layer between your POCO objects and your database with full SQL support.
++Get Started + GitHub ++++dotnet add package linq2db++++ + + + + + + +++ + + + + + + +++ ++++ +++++C# LINQ+var query = + from p in db.GetTable<Product>() + where p.Price > 100 + && p.CategoryId == 1 + orderby p.Name + select new + { + p.Name, + p.Price + };+++Generated SQL+SELECT + [p].[Name], + [p].[Price] +FROM + [Products] [p] +WHERE + [p].[Price] > 100 + AND [p].[CategoryId] = 1 +ORDER BY + [p].[Name]+++ +++++C# LINQ+var query = + from p in db.GetTable<Product>() + select new + { + Product = p.Name, + Category = p.Category.Name, + Orders = p.OrderItems.Count() + };+++Generated SQL+SELECT + [p].[Name], + [a_Category].[Name], + ( + SELECT COUNT(*) + FROM [OrderItems] [a_OrderItems] + WHERE + [p].[Id] = [a_OrderItems].[ProductId] + ) +FROM + [Products] [p] + LEFT JOIN [Categories] [a_Category] + ON [p].[CategoryId] = [a_Category].[Id]+++ +++++C# LINQ+// Reusable SQL expression +[ExpressionMethod(nameof(Impl))] +static decimal TotalRevenue(Product p) + => throw new InvalidOperationException(); + static Expression<Func<Product, decimal>> + Impl() => p => p.OrderItems + .Sum(oi => oi.Quantity * oi.UnitPrice); + // let avoids duplicate subqueries +var query = + from p in db.GetTable<Product>() + let total = TotalRevenue(p) + where total > 1000 + select new + { + p.Name, + Revenue = total + };+++Generated SQL+SELECT + [t1].[Name], + [t1].[total] +FROM + ( + SELECT + ( + SELECT SUM( + CAST([oi].[Quantity] + AS Decimal) + * [oi].[UnitPrice]) + FROM [OrderItems] [oi] + WHERE + [p].[Id] = [oi].[ProductId] + ) as [total], + [p].[Name] + FROM + [Products] [p] + ) [t1] +WHERE + [t1].[total] > 1000+++ +++++C# LINQ+var topProducts = + db.GetTable<Product>() + .Where(p => p.Price > 50) + .Select(p => new + { + p.Id, p.Name, + p.Price, p.CategoryId + }) + .AsCte("TopProducts"); + // CTE reused in join + subquery +var query = + from tp in topProducts + join c in db.GetTable<Category>() + on tp.CategoryId equals c.Id + select new + { + tp.Name, tp.Price, + Category = c.Name, + SameCategory = topProducts + .Count(x => x.CategoryId + == tp.CategoryId) + };+++Generated SQL+WITH [TopProducts] + ([CategoryId], [Name], [Price]) +AS +( + SELECT + [p].[CategoryId], + [p].[Name], + [p].[Price] + FROM [Products] [p] + WHERE [p].[Price] > 50 +) +SELECT + [tp].[Name], + [tp].[Price], + [c].[Name], + ( + SELECT COUNT(*) + FROM [TopProducts] [t1] + WHERE [t1].[CategoryId] + = [tp].[CategoryId] + ) +FROM + [TopProducts] [tp] + INNER JOIN [Categories] [c] + ON [tp].[CategoryId] = [c].[Id]+++ +++++C# LINQ+var query = + from p in db.GetTable<Product>() + select new + { + p.Name, + p.Price, + Category = p.Category.Name, + RowNum = Sql.Ext.RowNumber() + .Over() + .PartitionBy(p.CategoryId) + .OrderByDesc(p.Price) + .ToValue(), + Total = Sql.Ext.Sum(p.Price) + .Over() + .PartitionBy(p.CategoryId) + .ToValue() + };+++Generated SQL+SELECT + [p].[Name], + [p].[Price], + [a_Category].[Name], + ROW_NUMBER() OVER( + PARTITION BY [p].[CategoryId] + ORDER BY [p].[Price] DESC), + SUM([p].[Price]) OVER( + PARTITION BY [p].[CategoryId]) +FROM + [Products] [p] + LEFT JOIN [Categories] [a_Category] + ON [p].[CategoryId] + = [a_Category].[Id]+++ +++++C# LINQ+db.GetTable<Product>() + .Merge() + .Using(source) + .OnTargetKey() + .UpdateWhenMatched( + (target, src) => new Product + { + Name = src.Name, + Price = src.Price, + }) + .InsertWhenNotMatched( + src => new Product + { + Id = src.Id, + Name = src.Name, + Price = src.Price, + CategoryId = src.CategoryId, + }) + .Merge();+++Generated SQL+MERGE INTO [Products] [Target] +USING (VALUES + (1,N'Laptop',999,1), + (2,N'Tablet',499,1) +) [Source] +([Id],[Name],[Price],[CategoryId]) +ON ([Target].[Id] = [Source].[Id]) + WHEN MATCHED THEN +UPDATE SET + [Name] = [Source].[Name], + [Price] = [Source].[Price] + WHEN NOT MATCHED THEN +INSERT + ([Id],[Name],[Price],[CategoryId]) +VALUES + ([Source].[Id], + [Source].[Name], + [Source].[Price], + [Source].[CategoryId]) +;+++++++C# Code+// Populate temp table from data +var ids = Enumerable + .Range(1, 500) + .Select(i => new { Id = i }); + using var tmp = + db.CreateTempTable( + "#FilterIds", ids); + // Join temp table in LINQ query +var query = + from p in db.GetTable<Product>() + join t in tmp + on p.Id equals t.Id + orderby p.Name + select new + { + p.Name, + p.Price + };+++Generated SQL+-- 1. CREATE TABLE +CREATE TABLE [#FilterIds] +( + [Id] int NOT NULL +) + -- 2. BulkCopy 500 rows + -- 3. Query joins temp table +SELECT + [p].[Name], + [p].[Price] +FROM + [Products] [p] + INNER JOIN [#FilterIds] [t] + ON [p].[Id] = [t].[Id] +ORDER BY + [p].[Name] + -- 4. Dispose drops table++ + + ++ diff --git a/source/templates/custom/public/main.css b/source/templates/custom/public/main.css index 1b1f898..608e0b4 100644 --- a/source/templates/custom/public/main.css +++ b/source/templates/custom/public/main.css @@ -1 +1,428 @@ -.dropdown-menu { white-space:nowrap } \ No newline at end of file +.dropdown-menu { white-space:nowrap } + +/* Navbar logo */ +#logo { + height: 24px; + width: auto; + margin-right: 8px; +} + +/* ============================================================ + Landing page + ============================================================ */ + +/* Hero — full-width breakout */ +.landing-hero { + margin: -1.5rem calc(-50vw + 50%) 0; + padding: 3rem 2rem 2.5rem; + background: linear-gradient(135deg, #1a1a2e 0%, #16213e 40%, #0f3460 100%); + color: #e8e8f0; + overflow: hidden; + position: relative; +} + +[data-bs-theme="light"] .landing-hero { + background: linear-gradient(135deg, #0f3460 0%, #16213e 40%, #1a1a2e 100%); +} + +.landing-hero-inner { + position: relative; + z-index: 1; +} + +.landing-badge { + display: inline-block; + padding: 0.35rem 1rem; + border-radius: 2rem; + font-size: 0.85rem; + font-weight: 500; + letter-spacing: 0.02em; + background: rgba(255,255,255,0.1); + border: 1px solid rgba(255,255,255,0.15); + color: #a8b4ff; + text-decoration: none; + transition: background 0.2s, border-color 0.2s; +} + +.landing-badge:hover { + background: rgba(255,255,255,0.15); + border-color: rgba(255,255,255,0.25); + color: #c0caff; +} + +.landing-badge i { + margin-right: 0.3rem; +} + +.landing-title { + font-size: 3rem; + font-weight: 800; + line-height: 1.1; + letter-spacing: -0.02em; + margin-bottom: 1rem; + color: #fff; +} + +.landing-accent { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.landing-subtitle { + font-size: 1.05rem; + line-height: 1.6; + color: #b0b8d0; + max-width: 480px; + margin-bottom: 1.5rem; +} + +.landing-actions { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; +} + +.landing-btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border: none; + font-weight: 600; + padding: 0.75rem 1.75rem; + border-radius: 0.5rem; + transition: transform 0.2s, box-shadow 0.2s; +} + +.landing-btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4); + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.landing-btn-secondary { + border-color: rgba(255,255,255,0.25); + color: #e0e0f0; + font-weight: 600; + padding: 0.75rem 1.75rem; + border-radius: 0.5rem; +} + +.landing-btn-secondary:hover { + background: rgba(255,255,255,0.1); + border-color: rgba(255,255,255,0.4); + color: #fff; +} + +/* NuGet install box */ +.landing-install { + display: inline-block; + background: rgba(0,0,0,0.3); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 0.5rem; + padding: 0.5rem 1.25rem; + font-family: var(--bs-font-monospace); + font-size: 0.9rem; + color: #a8b4ff; + letter-spacing: 0.01em; +} + +.landing-install code { + color: inherit; + background: none; + padding: 0; +} + +/* Syntax colors */ +.code-keyword { color: #c792ea; } +.code-type { color: #82aaff; } +.code-string { color: #c3e88d; } +.code-number { color: #f78c6c; } + +/* ============================================================ + Interactive demo (pure CSS tabs) + ============================================================ */ +.demo-container { + background: rgba(0,0,0,0.35); + border: 1px solid rgba(255,255,255,0.08); + border-radius: 0.75rem; + overflow: hidden; + backdrop-filter: blur(10px); + box-shadow: 0 20px 60px rgba(0,0,0,0.3); +} + +.demo-radio { display: none; } + +.demo-tabs { + display: flex; + gap: 0; + background: rgba(255,255,255,0.05); + border-bottom: 1px solid rgba(255,255,255,0.06); + overflow-x: auto; + scrollbar-width: none; +} + +.demo-tabs::-webkit-scrollbar { display: none; } + +.demo-tab { + padding: 0.6rem 1rem; + font-size: 0.78rem; + font-weight: 500; + color: rgba(255,255,255,0.4); + cursor: pointer; + white-space: nowrap; + border-bottom: 2px solid transparent; + transition: color 0.2s, border-color 0.2s; + user-select: none; +} + +.demo-tab:hover { + color: rgba(255,255,255,0.7); +} + +/* Active tab highlighting via :checked */ +#demo-tab-1:checked ~ .demo-tabs label[for="demo-tab-1"], +#demo-tab-2:checked ~ .demo-tabs label[for="demo-tab-2"], +#demo-tab-3:checked ~ .demo-tabs label[for="demo-tab-3"], +#demo-tab-4:checked ~ .demo-tabs label[for="demo-tab-4"], +#demo-tab-5:checked ~ .demo-tabs label[for="demo-tab-5"], +#demo-tab-6:checked ~ .demo-tabs label[for="demo-tab-6"], +#demo-tab-7:checked ~ .demo-tabs label[for="demo-tab-7"] { + color: #a8b4ff; + border-bottom-color: #667eea; +} + +/* Panel visibility via :checked */ +.demo-panel { display: none; } + +#demo-tab-1:checked ~ .demo-panels .demo-panel-1, +#demo-tab-2:checked ~ .demo-panels .demo-panel-2, +#demo-tab-3:checked ~ .demo-panels .demo-panel-3, +#demo-tab-4:checked ~ .demo-panels .demo-panel-4, +#demo-tab-5:checked ~ .demo-panels .demo-panel-5, +#demo-tab-6:checked ~ .demo-panels .demo-panel-6, +#demo-tab-7:checked ~ .demo-panels .demo-panel-7 { + display: block; +} + +.demo-split { + display: grid; + grid-template-columns: 1fr 1fr; + min-height: 280px; +} + +@media (max-width: 767.98px) { + .demo-split { + grid-template-columns: 1fr; + } +} + +.demo-pane { + padding: 0; + position: relative; + overflow: hidden; +} + +.demo-pane + .demo-pane { + border-left: 1px solid rgba(255,255,255,0.06); +} + +@media (max-width: 767.98px) { + .demo-pane + .demo-pane { + border-left: none; + border-top: 1px solid rgba(255,255,255,0.06); + } +} + +.demo-pane-label { + font-size: 0.65rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + color: rgba(255,255,255,0.3); + padding: 0.5rem 1rem 0; +} + +.demo-code { + margin: 0; + padding: 0.5rem 1rem 1rem; + font-size: 0.75rem; + line-height: 1.6; + color: #c8cee0; + background: transparent !important; + border: none !important; + overflow-x: auto; + white-space: pre; +} + +.demo-sql { + color: #e0dcc8; +} + +.code-comment { color: #676e95; font-style: italic; } + +/* ============================================================ + Landing cards section + ============================================================ */ +.landing-cards { + margin: 0 calc(-50vw + 50%); + padding: 2.5rem 0; + background: var(--bs-body-bg); +} + +.landing-card { + display: flex; + flex-direction: column; + height: 100%; + padding: 2rem; + border-radius: 1rem; + background: var(--bs-body-bg); + border: 1px solid var(--bs-border-color); + text-decoration: none; + color: var(--bs-body-color); + transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease; +} + +.landing-card:hover { + transform: translateY(-6px); + box-shadow: 0 12px 40px rgba(102, 126, 234, 0.12); + border-color: #667eea; + color: var(--bs-body-color); + text-decoration: none; +} + +.landing-card-icon-wrap { + width: 56px; + height: 56px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 0.75rem; + background: linear-gradient(135deg, rgba(102,126,234,0.1) 0%, rgba(118,75,162,0.1) 100%); + margin-bottom: 1.25rem; + font-size: 1.5rem; + color: #667eea; +} + +.landing-card h3 { + font-size: 1.25rem; + font-weight: 700; + margin-bottom: 0.5rem; +} + +.landing-card p { + flex: 1; + color: var(--bs-secondary-color); + font-size: 0.95rem; + line-height: 1.6; + margin-bottom: 1rem; +} + +.landing-card-link { + font-weight: 600; + font-size: 0.9rem; + color: #667eea; +} + +.landing-card:hover .landing-card-link { + color: #764ba2; +} + +.landing-card-link .bi { + transition: transform 0.2s; +} + +.landing-card:hover .landing-card-link .bi { + transform: translateX(4px); +} + +/* ============================================================ + Features section + ============================================================ */ +.landing-features { + margin: 0 calc(-50vw + 50%); + padding: 2.5rem 0 3rem; + background: var(--bs-tertiary-bg); + border-top: 1px solid var(--bs-border-color); +} + +.landing-feature { + text-align: center; + padding: 1rem; +} + +.landing-feature-icon { + width: 52px; + height: 52px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 0.75rem; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #fff; + font-size: 1.3rem; + margin-bottom: 1rem; +} + +.landing-feature h4 { + font-size: 1.1rem; + font-weight: 700; + margin-bottom: 0.5rem; +} + +.landing-feature p { + font-size: 0.9rem; + color: var(--bs-secondary-color); + line-height: 1.6; + margin: 0; +} + +/* ============================================================ + Documentation hub cards (documentation/index.md) + ============================================================ */ +.doc-card { + border: 1px solid var(--bs-border-color); + transition: transform 0.2s ease, box-shadow 0.2s ease; + cursor: pointer; +} + +.doc-card:hover { + transform: translateY(-2px); + box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.08); + border-color: #667eea; +} + +.doc-card .card-title i { + margin-right: 0.5rem; + color: #667eea; +} + +/* ============================================================ + Responsive + ============================================================ */ +@media (max-width: 991.98px) { + .landing-title { + font-size: 2.5rem; + } + + .landing-hero { + padding: 3rem 0; + } + + .landing-code { + margin-top: 2rem; + } +} + +@media (max-width: 575.98px) { + .landing-title { + font-size: 2rem; + } + + .landing-actions { + flex-direction: column; + } + + .landing-actions .btn { + width: 100%; + } +} diff --git a/source/toc.yml b/source/toc.yml index 70da596..e857380 100644 --- a/source/toc.yml +++ b/source/toc.yml @@ -1,3 +1,5 @@ +- name: Documentation + href: documentation/ - name: Articles href: articles/ - name: API Documentation @@ -33,8 +35,5 @@ href: api/linq2db.efcore.9/ - name: Linq To DB for Entity Framework Core 10 href: api/linq2db.efcore.10/ -# don't produce anything -# - name: Linq To DB Compat -# href: api/linq2db.compat/ - name: Linq To DB ASP.NET Identity Provider href: api/linq2db.identity/ diff --git a/tools/sql-demo/README.md b/tools/sql-demo/README.md new file mode 100644 index 0000000..d6d361b --- /dev/null +++ b/tools/sql-demo/README.md @@ -0,0 +1,104 @@ +# Landing Page SQL Demo — Maintenance Guide + +The landing page at `source/index.md` features an interactive tabbed demo showing C# LINQ code alongside the real SQL generated by linq2db. This document explains how to maintain and extend it. + +## Architecture + +- **C# side**: Hand-written syntax-highlighted HTML using `` classes (`code-keyword`, `code-type`, `code-string`, `code-number`, `code-comment`) +- **SQL side**: Real SQL output generated by linq2db's `ToSqlQuery()` API, then hand-formatted with the same span classes +- **Tabs**: Pure CSS — hidden `` elements + `:checked` sibling selectors in `main.css` (no JavaScript) + +## How to generate SQL for a new or updated tab + +### 1. Copy `SqlDemoGenerator.cs` to linq2db Tests.Playground + +```bash +cp tools/sql-demo/SqlDemoGenerator.cs ../linq2db/Tests/Tests.Playground/SqlDemoGenerator.cs +``` + +### 2. Edit the test to add/modify queries + +The file defines entity models (`Product`, `Category`, `Order`, `OrderItem`) and test methods that generate SQL using: + +```csharp +var sql = query.ToSqlQuery(new SqlGenerationOptions { InlineParameters = true }).Sql; +Console.WriteLine(sql); +``` + +- SQLite connection (`CreateSQLiteConnection()`) works for most queries +- Merge requires SQL Server — use `[IncludeDataSources(TestProvName.AllSqlServer)]` attribute +- TempTable creates DDL (side effects), so its SQL is documented manually on the landing page + +### 3. Run the tests + +```bash +cd ../linq2db +dotnet test Tests/Tests.Playground/Tests.Playground.csproj \ + -c Debug -f net10.0 \ + --filter "FullyQualifiedName~SqlDemoGenerator" \ + -v n --nologo 2>&1 | grep -A 50 "=== " +``` + +This prints the SQL for each test. Copy the output. + +### 4. Copy the test file back to docs for reference + +```bash +cp ../linq2db/Tests/Tests.Playground/SqlDemoGenerator.cs tools/sql-demo/SqlDemoGenerator.cs +``` + +### 5. Update the landing page + +Edit `source/index.md`. Each tab consists of: + +```html + ++++ ++++ ++ ++High Performance
+Generates optimized SQL with minimal overhead. No reflection at runtime, no heavy object tracking.
+++ ++ ++Multi-Database
+SQL Server, PostgreSQL, MySQL, SQLite, Oracle, Firebird, ClickHouse, and many more.
+++ ++ ++Full LINQ
+Type-safe queries with advanced SQL: CTEs, Window Functions, MERGE, Bulk Copy, and more.
+++ ++ ++Extensible
+EF Core integration, ASP.NET Identity, gRPC, SignalR, and HTTP remoting out of the box.
+++``` + +### Adding a new tab + +1. Add a new `` with id `demo-tab-N` +2. Add a `++++C# LINQ+...syntax-highlighted C# here...+++Generated SQL+...syntax-highlighted SQL here...+