{"id":50360191,"url":"https://github.com/redbase-app/redb","last_synced_at":"2026-05-30T01:04:56.750Z","repository":{"id":340640110,"uuid":"1140860293","full_name":"redbase-app/redb","owner":"redbase-app","description":"Typed object storage for .NET. Schema = C# class. Full LINQ. Zero migrations. Postgres \u0026 MSSQL.","archived":false,"fork":false,"pushed_at":"2026-05-29T23:53:01.000Z","size":8697,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-30T00:05:17.438Z","etag":null,"topics":["csharp","database","dotnet","entity-framework-alternative","linq","mssql","orm","postgresql","sql-server"],"latest_commit_sha":null,"homepage":"https://redbase.app","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/redbase-app.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-23T21:08:37.000Z","updated_at":"2026-05-29T23:53:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/redbase-app/redb","commit_stats":null,"previous_names":["redbase-app/redb"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/redbase-app/redb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redbase-app%2Fredb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redbase-app%2Fredb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redbase-app%2Fredb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redbase-app%2Fredb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/redbase-app","download_url":"https://codeload.github.com/redbase-app/redb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/redbase-app%2Fredb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33676192,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-29T02:00:06.066Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","database","dotnet","entity-framework-alternative","linq","mssql","orm","postgresql","sql-server"],"created_at":"2026-05-30T01:04:56.060Z","updated_at":"2026-05-30T01:04:56.744Z","avatar_url":"https://github.com/redbase-app.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RedBase — typed object storage for .NET. Schema = C# class. Zero migrations. Full LINQ. Postgres \u0026 MSSQL.\n\n**Trees, lists, aggregations, windows — one LINQ API.**\n\nRedBase is a data platform for .NET that stores typed objects in a relational backend (PostgreSQL, Microsoft SQL Server). You define schemas as plain C# classes — RedBase handles storage, indexing, querying, trees, lists, aggregation, and window functions through a single LINQ-style API.\n\nNo Entity Framework. No migrations. No 40-table Include chains. Just C# classes and one line to save, one line to load.\n\n[![NuGet](https://img.shields.io/nuget/v/redb.Core?label=NuGet\u0026color=blue)](https://www.nuget.org/packages/redb.Core)\n[![Downloads](https://img.shields.io/nuget/dt/redb.Core?label=Downloads\u0026color=green)](https://www.nuget.org/packages/redb.Core)\n[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)\n[![.NET](https://img.shields.io/badge/.NET-8%20%7C%209%20%7C%2010-purple)](https://dotnet.microsoft.com)\n[![Website](https://img.shields.io/badge/docs-redbase.app-orange)](https://redbase.app)\n[![Website RU](https://img.shields.io/badge/docs-redb.ru-blue)](https://redb.ru)\n\n---\n\n## Why RedBase\n\n| Problem | EF Core / Dapper | RedBase |\n|---------|------------------|---------|\n| 28 related tables | 40+ Include/ThenInclude, 200 lines of config | `LoadAsync\u003cT\u003e(id)` — 1 line, full object graph |\n| Add a field | Create migration, update DbContext, deploy | Add property to C# class, done |\n| Tree structures | Manual recursive CTEs, no reusable API | Built-in `TreeQuery\u003cT\u003e()` with CTE, depth, ancestors |\n| Dynamic schema | Pain. EF doesn't support it | Native. Schema = C# class. `SyncSchemeAsync()` |\n| Learn curve | DbContext, Fluent API, migrations, conventions | One interface: `IRedbService`. One attribute: `[RedbScheme]` |\n| Forgot Include? | Runtime crash or silent null | Impossible — Props are always loaded |\n\n\u003e **Strong typing — real columns, not JSON blobs.** Every property of your `*Props` class maps to a dedicated typed column with a FK constraint and an index. `string` → `nvarchar`, `decimal` → `numeric(18,4)`, `DateTime` → `timestamptz`, arrays and dictionaries → normalised rows. Your data is queryable, filterable, and aggregatable at the SQL level — no `JSON_VALUE` hacks, no full-table JSON scans, no cast errors at runtime.\n\n---\n\n## Quick Start\n\n```csharp\n// 1. Define a schema — that's your \"migration\"\n[RedbScheme(\"Employee\")]\npublic class EmployeeProps\n{\n    public string FirstName { get; set; } = \"\";\n    public string LastName { get; set; } = \"\";\n    public int Age { get; set; }\n    public decimal Salary { get; set; }\n    public string Department { get; set; } = \"\";\n    public string Position { get; set; } = \"\";\n    public DateTime HireDate { get; set; }\n    public string[]? Skills { get; set; }\n    public Address? HomeAddress { get; set; }\n    public Dictionary\u003cint, decimal\u003e? BonusByYear { get; set; }\n}\n\n// 2. Sync scheme (creates storage automatically)\nawait redb.SyncSchemeAsync\u003cEmployeeProps\u003e();\n\n// 3. Save — from E001_SaveAsync.cs\nvar employee = new RedbObject\u003cEmployeeProps\u003e\n{\n    name = \"New Developer\",\n    Props = new EmployeeProps\n    {\n        FirstName = \"Alice\",\n        LastName = \"Johnson\",\n        Age = 28,\n        Position = \"Developer\",\n        Department = \"Engineering\",\n        Salary = 85000m,\n        HireDate = DateTime.Today,\n        Skills = [\"C#\", \"React\", \"SQL\"]\n    }\n};\nawait redb.SaveAsync(employee);\n\n// 4. Load — from E002_LoadAsync.cs\nvar loaded = await redb.LoadAsync\u003cEmployeeProps\u003e(employee.Id);\n// loaded.Props.FirstName → \"Alice\"\n\n// 5. Query — from E010_WhereSimple.cs\nvar results = await redb.Query\u003cEmployeeProps\u003e()\n    .Where(e =\u003e e.Salary \u003e 75000m)\n    .OrderByDescending(e =\u003e e.Salary)\n    .Take(100)\n    .ToListAsync();\n\n// 6. Projection — from E075_Select.cs\nvar projected = await redb.Query\u003cEmployeeProps\u003e()\n    .Select(x =\u003e new { x.Props.FirstName, x.Props.LastName, x.Props.Salary })\n    .ToListAsync();\n```\n\n### Create from Template\n\n```bash\ndotnet new install redb.Templates\ndotnet new redb -n MyProject --provider postgres\ncd MyProject\ndotnet run\n```\n\n---\n\n## Packages\n\n| Package | NuGet | Description |\n|---------|-------|-------------|\n| `redb.Core` | [![NuGet](https://img.shields.io/nuget/v/redb.Core?label=)](https://www.nuget.org/packages/redb.Core) | Core abstractions, query builder, LINQ provider |\n| `redb.Postgres` | [![NuGet](https://img.shields.io/nuget/v/redb.Postgres?label=)](https://www.nuget.org/packages/redb.Postgres) | PostgreSQL provider (free, Apache 2.0) |\n| `redb.MSSql` | [![NuGet](https://img.shields.io/nuget/v/redb.MSSql?label=)](https://www.nuget.org/packages/redb.MSSql) | Microsoft SQL Server provider (free, Apache 2.0) |\n| `redb.Core.Pro` | [![NuGet](https://img.shields.io/nuget/v/redb.Core.Pro?label=)](https://www.nuget.org/packages/redb.Core.Pro) | Pro extensions: parallel materialization, change tracking, migrations |\n| `redb.Postgres.Pro` | [![NuGet](https://img.shields.io/nuget/v/redb.Postgres.Pro?label=)](https://www.nuget.org/packages/redb.Postgres.Pro) | PostgreSQL Pro provider with optimized query generation |\n| `redb.MSSql.Pro` | [![NuGet](https://img.shields.io/nuget/v/redb.MSSql.Pro?label=)](https://www.nuget.org/packages/redb.MSSql.Pro) | MSSQL Pro provider with optimized query generation |\n| `redb.Export` | [![NuGet](https://img.shields.io/nuget/v/redb.Export?label=)](https://www.nuget.org/packages/redb.Export) | Database export/import: `.redb` files (JSONL/ZIP) for backup, migration between PostgreSQL ↔ MSSQL |\n\n---\n\n## Installation\n\n```bash\n# PostgreSQL (Free)\ndotnet add package redb.Postgres\n\n# or MSSQL (Free)\ndotnet add package redb.MSSql\n\n# Pro — includes redb.Core.Pro automatically\ndotnet add package redb.Postgres.Pro   # or redb.MSSql.Pro\n```\n\nEach provider package pulls in `redb.Core` (or `redb.Core.Pro`) as a transitive dependency.\n\n### Setup \u0026 InitializeAsync\n\n```csharp\nusing redb.Core;\nusing redb.Core.Extensions;\nusing redb.Postgres.Pro.Extensions;  // or redb.MSSql.Pro.Extensions\n\nvar builder = WebApplication.CreateBuilder(args);\n\n// Register REDB Pro\nbuilder.Services.AddRedbPro(options =\u003e options\n    .UsePostgres(\"Host=localhost;Database=mydb;Username=postgres;Password=pass\")\n    // .UseMsSql(\"Server=localhost;Database=mydb;User Id=sa;Password=pass;TrustServerCertificate=true\")\n    .WithLicense(\"YOUR-LICENSE-KEY\")     // optional — trial works without it\n    .Configure(c =\u003e\n    {\n        c.PropsSaveStrategy = PropsSaveStrategy.ChangeTracking;\n        c.EnableLazyLoadingForProps = false;\n        c.EnablePropsCache = true;\n    }));\n\nvar app = builder.Build();\n\n// Initialize and sync schemes\nvar redb = app.Services.GetRequiredService\u003cIRedbService\u003e();\nawait redb.InitializeAsync();\nawait redb.SyncSchemeAsync\u003cEmployeeProps\u003e();\n```\n\n**Free version** — same pattern, just `AddRedb` instead of `AddRedbPro`, no license:\n\n```csharp\nusing redb.Core.Extensions;\nusing redb.Postgres.Extensions;  // or redb.MSSql.Extensions\n\nbuilder.Services.AddRedb(options =\u003e options\n    .UsePostgres(\"Host=localhost;Database=mydb;Username=postgres;Password=pass\"));\n```\n\n### Database Setup Options\n\nREDB provides several ways to create the database schema:\n\n**Option A — Automatic on startup (recommended):**\n\n```csharp\n// Creates schema if missing, then initializes\nawait redb.InitializeAsync(ensureCreated: true);\n```\n\n**Option B — Explicit call:**\n\n```csharp\nawait redb.EnsureDatabaseAsync(); // idempotent — safe to call every time\nawait redb.InitializeAsync();\n```\n\n**Option C — Export SQL script for DBA / CI:**\n\n```csharp\nvar sql = redb.GetSchemaScript();\nFile.WriteAllText(\"redb_schema.sql\", sql);\n```\n\n**Option D — CLI tool:**\n\n```bash\ndotnet tool install --global redb.CLI\n\n# Create schema in an existing database\nredb init --connection \"Host=localhost;Database=mydb;...\" --provider postgres\n\n# Export SQL to file\nredb schema --provider postgres --output redb_schema.sql\n```\n\n---\n\n## Capabilities\n\n### CRUD\n\n| Feature | API | Notes |\n|---------|-----|-------|\n| Save single object | `SaveAsync(obj)` | Create or update |\n| Load by ID | `LoadAsync\u003cT\u003e(id)` | Returns `RedbObject\u003cT\u003e` with Props |\n| Bulk insert | `AddNewObjectsAsync(objects)` | Single round-trip via COPY protocol, replaces thousands of INSERTs |\n| Batch save | `SaveAsync(IEnumerable\u003cIRedbObject\u003e)` | Save multiple objects at once |\n| Delete | `DeleteAsync(id)`, `DeleteWithPurgeAsync(ids)` | Single or batch delete with full purge |\n| Load-modify-save | `LoadAsync` → modify → `SaveAsync` | Standard update pattern |\n\n### Object Graph (RedbObject References in Props)\n\nProps can contain `RedbObject\u003cT\u003e` properties — single, array, or dictionary. The entire graph is saved and loaded in one call. No JOINs, no Include chains, no manual assembly.\n\n```csharp\n// Real model from redb.Examples/Models/ExampleModels.cs\n[RedbScheme(\"Employee\")]\npublic class EmployeeProps\n{\n    public string FirstName { get; set; } = \"\";\n    public int Age { get; set; }\n    public decimal Salary { get; set; }\n\n    // Nested business class (Address with nested BuildingInfo)\n    public Address? HomeAddress { get; set; }\n\n    // Array of business classes\n    public Contact[]? Contacts { get; set; }\n\n    // RedbObject reference — single\n    public RedbObject\u003cProjectMetricsProps\u003e? CurrentProject { get; set; }\n\n    // RedbObject references — array\n    public RedbObject\u003cProjectMetricsProps\u003e[]? PastProjects { get; set; }\n\n    // Dictionary with RedbObject values\n    public Dictionary\u003cstring, RedbObject\u003cProjectMetricsProps\u003e\u003e? ProjectMetrics { get; set; }\n\n    // Dictionary with nested classes\n    public Dictionary\u003cstring, Department\u003e? DepartmentHistory { get; set; }\n\n    // Tuple key dictionary\n    public Dictionary\u003c(int Year, string Quarter), string\u003e? PerformanceReviews { get; set; }\n}\n\n// Save — entire graph persisted, ParentId set automatically\nawait redb.SaveAsync(employee);\n\n// Load — full graph reconstructed, all nested objects hydrated\nvar loaded = await redb.LoadAsync\u003cOrderProps\u003e(id);\n// loaded.Props.Payment.Props          — ready\n// loaded.Props.RelatedMetrics[0].Props — ready\n// loaded.Props.Coupons[\"SUMMER\"].Props — ready\n```\n\nNested objects are real `RedbObject` instances with their own `id`, `name`, `DateCreate`, `DateModify`, and `Props`. They can be queried independently, updated in place, and participate in tree structures.\n\n### Rich Props Structure\n\nProps fields can be any combination of scalars, nested classes, collections, and dictionaries — arbitrarily deep. Everything is serialized, stored, and fully restored on load. No extra tables, no FKs, no mapping.\n\n```csharp\n[RedbScheme(\"Analytics Record\")]\npublic class AnalyticsRecordProps\n{\n    // Scalars\n    public string Title { get; set; } = \"\";\n    public int Views { get; set; }\n    public decimal Revenue { get; set; }\n\n    // Primitive arrays\n    public string[]? Tags { get; set; }\n    public int[]? Scores { get; set; }\n\n    // Nested business class\n    public Address? HomeAddress { get; set; }\n\n    // Array of business classes\n    public Contact[]? Contacts { get; set; }\n\n    // Dictionary\u003cstring, primitive\u003e\n    public Dictionary\u003cstring, string\u003e? PhoneBook { get; set; }\n\n    // Dictionary\u003cint, decimal\u003e\n    public Dictionary\u003cint, decimal\u003e? PriceList { get; set; }\n\n    // Dictionary\u003cstring, nested class\u003e\n    public Dictionary\u003cstring, Address\u003e? AddressBook { get; set; }\n\n    // Dictionary\u003cstring, complex class with its own arrays and dicts\u003e\n    public Dictionary\u003cstring, ProjectInfo\u003e? ComplexBook { get; set; }\n\n    // Tuple key dictionary\n    public Dictionary\u003c(int, string), string\u003e? TupleKeyDict { get; set; }\n}\n\npublic class Address\n{\n    public string City { get; set; } = \"\";\n    public string Street { get; set; } = \"\";\n    public BuildingDetails? Details { get; set; }   // nesting goes deeper\n}\n\npublic class ProjectInfo\n{\n    public string Title { get; set; } = \"\";\n    public int Priority { get; set; }\n    public string[]? Tags { get; set; }             // arrays inside dict values\n    public MetricTag[]? MetricTags { get; set; }    // class arrays inside dict values\n    public Dictionary\u003cstring, int\u003e? Scores { get; set; } // dict inside dict value\n}\n```\n\nAll saved and loaded with a single `SaveAsync` / `LoadAsync`. Fields can be added or removed at any time — `SyncSchemeAsync` handles it.\n\n### Where Filters\n\n| Feature | Example | Notes |\n|---------|---------|-------|\n| Comparison | `.Where(e =\u003e e.Salary \u003e 75000)` | `\u003e`, `\u003c`, `\u003e=`, `\u003c=`, `==`, `!=` |\n| AND / OR / NOT | `.Where(e =\u003e e.Age \u003e= 30 \u0026\u0026 e.Salary \u003e 70000)` | Standard boolean logic |\n| Chained Where | `.Where(...).Where(...)` | Multiple calls = AND |\n| WhereIn | `.WhereIn(e =\u003e e.Department, values)` | SQL `IN (...)` clause |\n| Nullable fields | `.Where(e =\u003e e.Code == null)` | IS NULL / IS NOT NULL |\n| Base field filters | `.WhereRedb(o =\u003e o.DateCreate.Year == 2025)` | Filter on `_objects` table fields directly |\n| WhereInRedb | `.WhereInRedb(x =\u003e x.Id, ids)` | IN on base fields — direct |\n\n### DateTime\n\n| Feature | Example |\n|---------|---------|\n| Comparison | `.Where(e =\u003e e.HireDate \u003e= cutoffDate)` |\n| Range | `.Where(e =\u003e e.HireDate \u003e= start \u0026\u0026 e.HireDate \u003c end)` |\n| Extract parts | `.WhereRedb(o =\u003e o.DateCreate.Year == 2025)` |\n\n### String Operations\n\n| Feature | Example | SQL |\n|---------|---------|-----|\n| Contains | `.Where(e =\u003e e.Name.Contains(\"Smith\"))` | `LIKE '%Smith%'` |\n| StartsWith | `.Where(e =\u003e e.Name.StartsWith(\"John\"))` | `LIKE 'John%'` |\n| Case-insensitive | `.Contains(\"smith\", StringComparison.OrdinalIgnoreCase)` | `ILIKE` |\n| ToLower / ToUpper | `.Where(e =\u003e e.Name.ToLower().Contains(\"s\"))` | `LOWER()` |\n| Trim + Length | `.Where(e =\u003e e.Name.Trim().Length \u003e 3)` | `TRIM()`, `LENGTH()` |\n\n### Nested Properties\n\n| Feature | Example |\n|---------|---------|\n| Nested class field | `.Where(e =\u003e e.HomeAddress!.City == \"London\")` |\n| Deep nesting (3+ levels) | `.Where(e =\u003e e.HomeAddress!.Building!.Floor \u003e 10)` |\n\n### Array Operations\n\n| Feature | Example |\n|---------|---------|\n| Contains element | `.Where(e =\u003e e.Skills.Contains(\"C#\"))` |\n| Contains any (OR) | `.Where(e =\u003e e.Skills.Contains(\"C#\") \\|\\| e.Skills.Contains(\"Python\"))` |\n| Contains all (AND) | `.Where(e =\u003e e.Skills.Contains(\"C#\") \u0026\u0026 e.Skills.Contains(\"SQL\"))` |\n| Exclude element | `.Where(e =\u003e e.Skills.Contains(\"C#\") \u0026\u0026 !e.Skills.Contains(\"intern\"))` |\n| Mixed with scalars | `.Where(e =\u003e e.Age \u003e 30 \u0026\u0026 e.Skills.Contains(\"C#\"))` |\n\n### Dictionary Operations\n\n| Feature | Example |\n|---------|---------|\n| ContainsKey | `.Where(e =\u003e e.PhoneDirectory!.ContainsKey(\"desk\"))` |\n| Indexer filter | `.Where(e =\u003e e.BonusByYear![2023] \u003e 6000)` |\n| Nested class in value | `.Where(e =\u003e e.Locations![\"HQ\"].City == \"NY\")` |\n| Tuple key | `.Where(e =\u003e e.Reviews![reviewKey] == \"Excellent\")` |\n\n### Projection \u0026 Pagination\n\n| Feature | API | Notes |\n|---------|-----|-------|\n| Select fields | `.Select(x =\u003e new { x.Props.FirstName, x.Props.Salary })` | Server-side projection, fetches only selected columns |\n| Distinct | `.Distinct()` | Dedup by Props hash |\n| DistinctBy | `.DistinctBy(x =\u003e x.Field)`, `.DistinctByRedb(x =\u003e x.Name)` | Distinct on specific field |\n| OrderBy | `.OrderBy(e =\u003e e.Salary)`, `.OrderByDescending(...)` | Ascending / descending |\n| ThenBy | `.ThenBy(...)`, `.ThenByDescending(...)` | Multi-field sorting |\n| Skip / Take | `.Skip(10).Take(10)` | Pagination |\n| FirstOrDefault | `.FirstOrDefaultAsync()` | Single result or null |\n\n### Existence \u0026 Count\n\n| Feature | API |\n|---------|-----|\n| Count | `.CountAsync()` |\n| Any | `.AnyAsync()`, `.AnyAsync(predicate)` |\n| All | `.AllAsync(predicate)` |\n\n### Arithmetic \u0026 Math\n\n| Feature | Example | SQL |\n|---------|---------|-----|\n| Arithmetic in Where | `.Where(e =\u003e e.Salary * 12 \u003e 1_000_000)` | Inline `*`, `/`, `+`, `-` |\n| Multi-field formulas | `.Where(e =\u003e e.Age * 1000 + e.Salary \u003e 120_000)` | Combined expressions |\n| Math.Abs | `.Where(e =\u003e Math.Abs(e.Age - 35) \u003c= 5)` | `ABS()` |\n| Arbitrary SQL function | `Sql.Function\u003cT\u003e(\"COALESCE\", e.Age, 0)` | Any SQL function by name |\n\n### Aggregation\n\n| Feature | API | Notes |\n|---------|-----|-------|\n| Sum | `.SumAsync(e =\u003e e.Salary)` | Server-side `SUM` |\n| Average | `.AverageAsync(e =\u003e e.Age)` | Server-side `AVG` |\n| Min / Max | `.MinAsync(e =\u003e e.Salary)`, `.MaxAsync(...)` | Server-side `MIN` / `MAX` |\n| Batch aggregation | `.AggregateAsync(x =\u003e new { Agg.Sum(...), Agg.Average(...), Agg.Count() })` | Multiple aggregations in one query |\n| Filtered aggregation | `.Where(...).SumAsync(...)` | Filter before aggregating |\n| Base-field aggregation | `.SumRedbAsync(x =\u003e x.Id)`, `.MinRedbAsync(x =\u003e x.DateCreate)` | Aggregate on `_objects` fields — direct |\n| Array element aggregation | `Agg.Sum(x.Props.SkillLevels.Select(s =\u003e s))` | Sum all array elements |\n\n### GroupBy\n\n| Feature | API | Notes |\n|---------|-----|-------|\n| Group by field | `.GroupBy(x =\u003e x.Department).SelectAsync(g =\u003e new { g.Key, Agg.Sum(g, x =\u003e x.Salary) })` | Server-side GROUP BY |\n| Filter + group | `.Where(...).GroupBy(...)` | WHERE before GROUP BY |\n| Composite key | `.GroupBy(x =\u003e new { x.Department, x.Position })` | Multi-field grouping |\n| Group by base field | `.GroupByRedb(x =\u003e x.OwnerId)` | GROUP BY on `_objects` — direct |\n| Group by array element | `.GroupByArray(e =\u003e e.Contacts!, c =\u003e c.Type)` | Expands arrays into groups |\n\n### Window Functions\n\n| Feature | API | Notes |\n|---------|-----|-------|\n| ROW_NUMBER | `Win.RowNumber()` | Rank within partition |\n| RANK / DENSE_RANK | `Win.Rank()`, `Win.DenseRank()` | Ranking with/without gaps |\n| Running sum | `Win.Sum(x.Props.Salary)` | Cumulative aggregation |\n| LAG / LEAD | `Win.Lag(x.Props.Salary)`, `Win.Lead(...)` | Previous / next row values |\n| FIRST_VALUE / LAST_VALUE | `Win.FirstValue(...)`, `Win.LastValue(...)` | Boundary values in partition |\n| NTILE | `Win.Ntile(4)` | Divide into equal buckets |\n| Custom frame | `.Frame(Frame.Rows(3))` | Sliding window: `ROWS BETWEEN N PRECEDING AND CURRENT ROW` |\n| Partition by base field | `.PartitionByRedb(x =\u003e x.SchemeId)` | Direct, no Props join |\n| Filter + window | `.Where(...).WithWindow(...)` | Pre-filter before windowing |\n| GroupBy + window | `.GroupBy(...).WithWindow(...).SelectAsync(...)` | Rank aggregated groups |\n\n### Tree Structures\n\nBuilt-in hierarchical data with closure-table storage and recursive CTEs.\n\n| Feature | API | Notes |\n|---------|-----|-------|\n| Create child | `CreateChildAsync(child, parent)` | Single node creation |\n| Bulk create | `AddNewObjectsAsync(treeObjects)` | Batch insert with pre-assigned IDs |\n| Load tree | `LoadTreeAsync\u003cT\u003e(root, maxDepth)` | Full hierarchy with depth limit |\n| Get children | `GetChildrenAsync\u003cT\u003e(parent)` | Direct children only |\n| Get descendants | `GetDescendantsAsync\u003cT\u003e(node)` | All descendants recursively |\n| Path to root | `GetPathToRootAsync\u003cT\u003e(node)` | Breadcrumb trail |\n| Move subtree | `MoveObjectAsync(node, newParent)` | Reparent node and its subtree |\n| Tree LINQ queries | `TreeQuery\u003cT\u003e().Where(...).OrderBy(...)` | Full LINQ on tree |\n| Filter roots | `.WhereRoots()` | Root nodes only |\n| Filter leaves | `.WhereLeaves()` | Leaf nodes only |\n| Filter by level | `.WhereLevel(2)` | Nodes at specific depth |\n| Scoped subtree query | `TreeQuery\u003cT\u003e(rootId, maxDepth)` | Query within a subtree |\n| Multiple roots | `TreeQuery\u003cT\u003e(parents[], maxDepth)` | Query across subtrees |\n| Ancestor filter | `.WhereHasAncestor\u003cT\u003e(a =\u003e a.Budget \u003e 500000)` | Filter by ancestor properties |\n| Descendant filter | `.WhereHasDescendant\u003cT\u003e(d =\u003e d.Budget \u003e 100000)` | Filter by descendant properties |\n| Relationship check | `node.IsDescendantOfAsync(ancestor)` | Without loading tree |\n| DFS / BFS traversal | `.DepthFirstTraversal()`, `.BreadthFirstTraversal()` | In-memory traversal |\n| Tree materialization | `.ToTreeListAsync()`, `.ToRootListAsync()`, `.ToFlatListAsync()` | Parent chains, recursive children, or flat |\n| Tree stats | `TreeCollection\u003cT\u003e.GetStats()` | Depth, leaf count, max width |\n| Aggregation on trees | `TreeQuery\u003cT\u003e().GroupBy(...)`, `.WithWindow(...)` | GroupBy and window functions in tree context |\n\n### Lists (Reference Dictionaries)\n\nNamed lookup lists with items, useful for statuses, categories, roles.\n\n| Feature | API |\n|---------|-----|\n| Create list | `RedbList.Create(name, alias)` |\n| Add items | `ListProvider.AddItemsAsync(list, values, aliases)` |\n| Get by name | `ListProvider.GetListByNameAsync(name)` |\n| Item with linked object | `RedbListItem(list, value, alias, linkedObject)` |\n| Store in Props | `Props.Status = listItem` (single), `Props.Roles = listItems` (array) |\n| Filter by item value | `.Where(p =\u003e p.Status!.Value == \"Active\")` |\n| Filter by item identity | `.Where(p =\u003e p.Status == activeItem)` |\n| WhereIn on item values | `.WhereIn(p =\u003e p.Status!.Value, values)` |\n| Any in item array | `.Where(p =\u003e p.Roles!.Any(r =\u003e r.Value == \"Admin\"))` |\n\n### Export / Import (Database Portability)\n\nFull database export/import via `.redb` files (JSONL, optionally ZIP-compressed). Migrate between PostgreSQL and MSSQL, create backups, replicate data.\n\n| Feature | Notes |\n|---------|-------|\n| Export entire DB | `ExportService` — streams all schemes, objects, values, users, roles, permissions |\n| Export filtered | Export only selected schemes by ID |\n| Import | `ImportService` — streaming JSONL, bulk-insert batches |\n| Compression | Optional ZIP wrapping (auto-detected on import) |\n| Cross-platform | Export from PostgreSQL → Import to MSSQL (and vice versa) |\n| Dry run | Preview statistics without writing |\n| CLI | `redb export` / `redb import` commands |\n\n### Users, Roles \u0026 Permissions\n\nBuilt-in user management with password hashing, roles, and object-level permissions. No external identity provider required.\n\n| Feature | API | Notes |\n|---------|-----|-------|\n| Create user | `UserProvider.CreateUserAsync(request)` | Login, password, name, email, phone |\n| Authenticate | `UserProvider.ValidateUserAsync(login, password)` | Returns `IRedbUser?`, SHA256 + salt |\n| Change password | `UserProvider.ChangePasswordAsync(userId, old, new)` | Verifies old password first |\n| Enable / disable | `UserProvider.EnableUserAsync(id)`, `DisableUserAsync(id)` | Soft disable |\n| Search users | `UserProvider.GetUsersAsync(criteria)` | Filter by login, email, role, date range |\n| Create role | `RoleProvider.CreateRoleAsync(request)` | Named role with optional description |\n| Assign role | `RoleProvider.AssignUserToRoleAsync(userId, roleId)` | Many-to-many |\n| Grant permission | `GrantPermissionAsync(request)` | Per-object or per-scheme, CRUD flags |\n| Check permission | `CanUserSelectObject(objectId)` | Select / Insert / Update / Delete |\n| Effective permissions | `GetEffectivePermissionsAsync(userId, objectId)` | Resolved from user + role inheritance |\n| Security context | `SetCurrentUser(user)`, `CreateSystemContext()` | Ambient via `AsyncLocal`, disposable elevation |\n\n```csharp\n// Authenticate\nvar user = await redb.UserProvider.ValidateUserAsync(\"admin\", \"password123\");\nif (user != null)\n{\n    redb.SetCurrentUser(user);\n    // All subsequent operations run as this user\n}\n\n// Temporary system elevation (skip permissions)\nusing (redb.CreateSystemContext())\n{\n    await redb.SaveAsync(sensitiveObject);\n}\n```\n\n---\n\n## Architecture\n\n```mermaid\ngraph TD\n    A[\"IRedbService\"] --\u003e B[\"Query\u0026lt;T\u0026gt;()\"]\n    A --\u003e C[\"TreeQuery\u0026lt;T\u0026gt;()\"]\n    A --\u003e D[\"SaveAsync / LoadAsync\"]\n    A --\u003e E[\"SyncSchemeAsync\"]\n    A --\u003e F[\"ListProvider\"]\n    A --\u003e G[\"UserProvider / RoleProvider\"]\n    \n    B --\u003e H[\"ExpressionToSqlCompiler\"]\n    C --\u003e I[\"CTE recursive queries\"]\n    D --\u003e J[\"IRedbContext (SQL)\"]\n    E --\u003e K[\"Auto-migration\"]\n    \n    H --\u003e L[\"PostgreSQL / MSSQL\"]\n    I --\u003e L\n    J --\u003e L\n    K --\u003e L\n```\n\n```\nIRedbService          ← single entry point\n├── Query\u003cT\u003e()        ← flat LINQ queries  \n├── TreeQuery\u003cT\u003e()    ← hierarchical queries (CTE)\n├── SaveAsync()       ← create / update (full object graph)\n├── LoadAsync\u003cT\u003e()    ← load by ID (with all nested objects)\n├── ListProvider      ← reference dictionaries\n├── UserProvider      ← users, authentication, passwords\n├── RoleProvider      ← roles and user-role assignments\n├── SecurityContext   ← current user, permission checks\n└── SyncSchemeAsync() ← auto-migration from C# class\n```\n\nStorage is provider-based. Each `[RedbScheme]` class maps to an internal structure in the target database. Schema changes (new fields, removed fields, type changes) are handled automatically by `SyncSchemeAsync` — no migration files needed.\n\n**Supported backends:** PostgreSQL 14+, Microsoft SQL Server 2019+.\n\n### Schema lifecycle and multi-version deployments\n\n**Read path is graceful.** When deployed code has a `Props` class without a field\nthat exists in the database, the materializer silently skips it. Old binaries\nsafely read newer data — no exception is thrown.\n\n**Write path is destructive by default.** `InitializeAsync()` runs\n`AutoSyncSchemesAsync()` which calls `SyncSchemeAsync\u003cT\u003e()` per scheme. By\ndefault this **deletes** any `_structures` row not present in the C# Props\nclass, and every `_values` row referencing those structures is silently\nremoved as well:\n\n- **PostgreSQL** — the FK `_values._id_structure -\u003e _structures._id` is\n  declared `ON DELETE CASCADE`.\n- **MSSQL** — the FK is `NO ACTION` (MSSQL forbids multiple cascade paths\n  into `_values`), but the trigger `TR__structures__cascade_values`\n  (`INSTEAD OF DELETE` on `_structures`) deletes the dependent `_values`\n  rows first, producing the same runtime effect as PostgreSQL.\n\nFor rolling / blue-green deployments where old and new app versions may share\nthe database, disable destructive sync:\n\n```csharp\nservices.AddRedb(options =\u003e options\n    .UsePostgres(connectionString)\n    .Configure(c =\u003e c.DefaultStrictDeleteExtra = false));\n```\n\nWhen destructive sync is enabled and structures are actually removed, an\n`ILogger.LogWarning` is emitted listing the scheme name and the structure\nids / names being deleted.\n\nThe per-save value-set ownership contract (one object's `SaveAsync` rewrites\nits full value set) is still destructive in this release — old and new versions\nshould not write to the same object during the multi-version window. An opt-in\n`PropsSaveMode.PreserveUnknownStructures` flag is planned; see ROADMAP.\n\n---\n\n## How It Compares\n\n### vs Entity Framework Core\n\n```csharp\n// EF Core — 28 related entities\nvar order = await context.Orders\n    .Include(o =\u003e o.Customer)\n    .Include(o =\u003e o.Items).ThenInclude(i =\u003e i.Product).ThenInclude(p =\u003e p.Category)\n    .Include(o =\u003e o.Items).ThenInclude(i =\u003e i.Discounts)\n    .Include(o =\u003e o.Shipping).ThenInclude(s =\u003e s.Address)\n    .Include(o =\u003e o.Payment).ThenInclude(p =\u003e p.Transactions)\n    // ... 35 more Include lines ...\n    .FirstOrDefaultAsync(o =\u003e o.Id == orderId);\n\n// RedBase — same data\nvar order = await redb.LoadAsync\u003cOrderProps\u003e(orderId);\n// All nested objects, arrays, dictionaries — loaded automatically.\n```\n\n### vs MongoDB / JSONB\n\n| Aspect | MongoDB/CosmosDB | RedBase |\n|--------|------------------|---------|\n| Type safety | `dynamic`, `BsonDocument` | `RedbObject\u003cT\u003e` — full IntelliSense |\n| LINQ support | Partial | Full (Where, GroupBy, Window, Aggregation) |\n| Transactions | Limited | Full ACID |\n| Referential integrity | None | Built-in |\n| Vendor lock-in | Yes | No (PostgreSQL / MSSQL) |\n\n---\n\n## Pro\n\nREDB Pro unlocks compiled query execution, parallel materialization, deep nested property queries, arithmetic and math expressions in WHERE, `Sql.Function\u003cT\u003e()` for calling arbitrary SQL functions, change tracking, schema migrations, and advanced analytics (window functions over grouped data). If performance matters — use Pro.\n\n**[redbase.app/pricing](https://redbase.app/pricing)**\n\n---\n\n## Documentation\n\n| Resource | Link |\n|----------|------|\n| Website \u0026 Docs (EN) | [redbase.app](https://redbase.app) |\n| Website \u0026 Docs (RU) | [redb.ru](https://redb.ru) |\n| API Reference | [redbase-app.github.io/redb](https://redbase-app.github.io/redb/) |\n| Architecture | [redbase.app/architecture](https://redbase.app/architecture) |\n| Quick Start | [redbase.app/quickstart](https://redbase.app/quickstart) |\n| Pricing | [redbase.app/pricing](https://redbase.app/pricing) |\n| Changelog | [CHANGELOG.md](CHANGELOG.md) |\n| NuGet | [nuget.org/packages/redb.Core](https://www.nuget.org/packages/redb.Core) |\n\n---\n\n## License\n\nCore packages (`redb.Core`, `redb.Postgres`, `redb.MSSql`, `redb.Export`,\n`redb.CLI`, `redb.Templates`, `redb.PropsEditor`) are licensed under\n[Apache License 2.0](LICENSE) starting from version 2.0.0.\nVersions ≤ 1.3.0 published on nuget.org remain under MIT.\n\nPro packages require a commercial license — see [LICENSE-PRO.txt](LICENSE-PRO.txt).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredbase-app%2Fredb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredbase-app%2Fredb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredbase-app%2Fredb/lists"}