Code Examples

Data Model - EmployeeProps Click to view data model used in all examples
Tag with priority for categorization.
/// <summary>
/// Tag with priority for categorization.
/// </summary>
public class Tag
{
    public string Name { get; set; } = string.Empty;
    public int Priority { get; set; }
    public string? Description { get; set; }
}

/// <summary>
/// Project metrics for analytics.
/// </summary>
[RedbScheme("ProjectMetrics")]
public class ProjectMetricsProps
{
    public long ProjectId { get; set; }
    public long? TasksCompleted { get; set; }
    public long? TasksTotal { get; set; }
    public long? BugsFixed { get; set; }
    public double? Budget { get; set; }
    public long? TeamSize { get; set; }
    public Tag[]? Tags { get; set; }
    public string[]? Technologies { get; set; }
}

/// <summary>
/// Address with city, street and building details.
/// </summary>
public class Address
{
    public string City { get; set; } = string.Empty;
    public string Street { get; set; } = string.Empty;
    public BuildingInfo? Building { get; set; }
}

/// <summary>
/// Building information with floor and amenities.
/// </summary>
public class BuildingInfo
{
    public int Floor { get; set; }
    public string Name { get; set; } = string.Empty;
    public string[]? Amenities { get; set; }
    public int[]? AccessCodes { get; set; }
    public string[]? ParkingSpots { get; set; }
    public int[]? ElevatorFloors { get; set; }
}

/// <summary>
/// Contact detail key-value pair.
/// </summary>
public class ContactDetail
{
    public string Label { get; set; } = string.Empty;
    public string Value { get; set; } = string.Empty;
}

/// <summary>
/// Contact information (email, phone, etc).
/// </summary>
public class Contact
{
    public string Type { get; set; } = string.Empty;
    public string Value { get; set; } = string.Empty;
    public bool IsVerified { get; set; }
    public int[]? NotificationHours { get; set; }
    public ContactDetail[]? Metadata { get; set; }
}

/// <summary>
/// Department info with tags, metrics and nested budget data.
/// </summary>
public class Department
{
    public string Name { get; set; } = string.Empty;
    public int HeadCount { get; set; }
    public string[] Projects { get; set; } = [];
    public Tag[] Leaders { get; set; } = [];
    public Dictionary<string, int>? BudgetByYear { get; set; }
}

/// <summary>
/// Employee props - main model for examples.
/// Demonstrates all supported types:
/// - Simple types (int, string, DateTime, long, decimal)
/// - Arrays (string[], int[])
/// - Business classes (Address with nested BuildingInfo)
/// - Array of business classes (Contact[])
/// - RedbObject references (CurrentProject, PastProjects[])
/// - Dictionary types (various key and value types)
/// </summary>
[RedbScheme("Employee")]
public class EmployeeProps
{
    // Basic info
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public int Age { get; set; }
    public DateTime HireDate { get; set; }
    public string Position { get; set; } = string.Empty;
    public decimal Salary { get; set; }
    public string Department { get; set; } = string.Empty;
    public string? EmployeeCode { get; set; }

    // Skills and certifications (arrays)
    public string[]? Skills { get; set; }
    public int[]? SkillLevels { get; set; }
    public string[]? Certifications { get; set; }
    public int[]? CertificationYears { get; set; }

    // Addresses (business classes)
    public Address? HomeAddress { get; set; }
    public Address? WorkAddress { get; set; }
    public Address? EmergencyAddress { get; set; }

    // Contacts (array of business classes)
    public Contact[]? Contacts { get; set; }

    // Project references (RedbObject)
    public RedbObject<ProjectMetricsProps>? CurrentProject { get; set; }
    public RedbObject<ProjectMetricsProps>[]? PastProjects { get; set; }

    // Phone directory: extension -> phone number
    public Dictionary<string, string>? PhoneDirectory { get; set; }

    // Office locations by city
    public Dictionary<string, Address>? OfficeLocations { get; set; }

    // Bonus history by year
    public Dictionary<int, decimal>? BonusByYear { get; set; }

    // Department details with complex nested data (Pro)
    public Dictionary<string, Department>? DepartmentHistory { get; set; }

    // Composite key: (year, quarter) -> performance score (Pro)
    public Dictionary<(int Year, string Quarter), string>? PerformanceReviews { get; set; }

    // Project metrics by code (Pro)
    public Dictionary<string, RedbObject<ProjectMetricsProps>>? ProjectMetrics { get; set; }
}

/// <summary>
/// Department props for tree examples (corporate hierarchy).
/// Used with TreeRedbObject for organizational structure.
/// Similar to CategoryTestProps in ConsoleTest.
/// </summary>
[RedbScheme("Department")]
public class DepartmentProps
{
    /// <summary>Department name (e.g. "IT Department Moscow").</summary>
    public string Name { get; set; } = string.Empty;

    /// <summary>Description (e.g. "Software development team").</summary>
    public string Description { get; set; } = string.Empty;

    /// <summary>Is department active?</summary>
    public bool IsActive { get; set; } = true;

    /// <summary>Budget in USD.</summary>
    public decimal Budget { get; set; }

    /// <summary>Department code (e.g. "IT-MSK-DEV").</summary>
    public string Code { get; set; } = string.Empty;
}

/// <summary>
/// City props for list examples (linked objects).
/// Used as target object for RedbListItem.Object reference.
/// </summary>
[RedbScheme("City")]
public class CityProps
{
    /// <summary>City name.</summary>
    public string Name { get; set; } = string.Empty;
    
    /// <summary>Population count.</summary>
    public int Population { get; set; }
    
    /// <summary>Region or district.</summary>
    public string Region { get; set; } = string.Empty;
    
    /// <summary>Is capital city?</summary>
    public bool IsCapital { get; set; }
    
    /// <summary>GPS coordinates [lat, lon].</summary>
    public double[] Coordinates { get; set; } = [];
}

/// <summary>
/// Person props demonstrating ListItem fields.
/// Shows single ListItem and List of ListItems usage.
/// </summary>
[RedbScheme("Person")]
public class PersonProps
{
    /// <summary>Person name.</summary>
    public string Name { get; set; } = string.Empty;
    
    /// <summary>Age in years.</summary>
    public int Age { get; set; }
    
    /// <summary>Email address.</summary>
    public string Email { get; set; } = string.Empty;
    
    /// <summary>Single ListItem field (e.g. status from dictionary).</summary>
    public RedbListItem? Status { get; set; }
    
    /// <summary>Array of ListItems (e.g. roles from dictionary).</summary>
    public List<RedbListItem>? Roles { get; set; }
}

/// <summary>
/// Product with a unique business key (V4 [RedbUnique]): the SKU is unique per scheme,
/// enforced by the database over the hash of the canonical value. Used by E003.
/// </summary>
public class ProductProps
{
    /// <summary>Unique business key. NULL never participates in uniqueness.</summary>
    [RedbUnique]
    public string? Sku { get; set; }

    /// <summary>Display name (not unique).</summary>
    public string Title { get; set; } = string.Empty;

    /// <summary>Price in USD.</summary>
    public decimal Price { get; set; }
}

SQL dump for smoke cases (Pro vs PVT)

Dumps SQL generated for a representative set of LINQ queries that mirror
the cases in redb.Postgres/sql/v2-pvt/99_smoke_auto.sql.
Designed so that running it against the Pro provider produces a SQL log
that can be compared side-by-side with the SQL produced by free v2-pvt
(pvt_build_query_sql).
The example does not execute any data queries. It only invokes
for each
query and writes the resulting SQL to:
  • the console (RAISE-style banner per case);
  • a text file at Output/smoke_sql_dump.txt in the example output
directory, so it can be diffed with the v2-pvt INSPECT block.
var sw = Stopwatch.StartNew();

var log = new StringBuilder();
int ok = 0, fail = 0;

Task EmployeeAsync(string label, Func<IRedbQueryable<EmployeeProps>, IRedbQueryable<EmployeeProps>> build)
    => DumpAsync<EmployeeProps>(label, () => build(redb.Query<EmployeeProps>()).Take(100));

Task PersonAsync(string label, Func<IRedbQueryable<PersonProps>, IRedbQueryable<PersonProps>> build)
    => DumpAsync<PersonProps>(label, () => build(redb.Query<PersonProps>()).Take(100));

Task CityAsync(string label, Func<IRedbQueryable<CityProps>, IRedbQueryable<CityProps>> build)
    => DumpAsync<CityProps>(label, () => build(redb.Query<CityProps>()).Take(100));

Task DepartmentAsync(string label, Func<IRedbQueryable<DepartmentProps>, IRedbQueryable<DepartmentProps>> build)
    => DumpAsync<DepartmentProps>(label, () => build(redb.Query<DepartmentProps>()).Take(100));

async Task DumpAsync<TProps>(string label, Func<IRedbQueryable<TProps>> build) where TProps : class, new()
{
    log.AppendLine("-----------------------------------------------------------------");
    log.AppendLine($"-- {label}");
    log.AppendLine("-----------------------------------------------------------------");
    IRedbQueryable<TProps>? q = null;
    try
    {
        q = build();
    }
    catch (Exception ex)
    {
        log.AppendLine($"-- BUILD FAIL: {ex.GetType().Name}: {ex.Message}");
        Console.WriteLine($"[BUILD-FAIL] {label}: {ex.Message}");
        fail++;
        log.AppendLine();
        return;
    }
    try
    {
        var sql = await q.ToSqlStringAsync();
        log.AppendLine(sql);
        Console.WriteLine($"[OK]   {label}");
        ok++;
    }
    catch (Exception ex)
    {
        log.AppendLine($"-- SQL FAIL: {ex.GetType().Name}: {ex.Message}");
        Console.WriteLine($"[FAIL] {label}: {ex.Message}");
        fail++;
    }
    log.AppendLine();
}

// ---------------- Employee: scalar predicates --------------------
await EmployeeAsync("eq string",                q => q.Where(e => e.FirstName == "Ivan"));
await EmployeeAsync("ne string",                q => q.Where(e => e.FirstName != "Ivan"));
await EmployeeAsync("in string",                q => q.Where(e => new[] { "IT", "HR" }.Contains(e.Department)));
await EmployeeAsync("nin string",               q => q.Where(e => !new[] { "Sales" }.Contains(e.Department)));
await EmployeeAsync("range int",                q => q.Where(e => e.Age >= 18 && e.Age <= 65));
await EmployeeAsync("numeric gt decimal",       q => q.Where(e => e.Salary > 1000m));
await EmployeeAsync("numeric lt decimal",       q => q.Where(e => e.Salary < 1_000_000m));
await EmployeeAsync("numeric range",            q => q.Where(e => e.Salary >= 0m && e.Salary <= 1_000_000m));
await EmployeeAsync("null check",               q => q.Where(e => e.EmployeeCode == null));
await EmployeeAsync("notNull check",            q => q.Where(e => e.EmployeeCode != null));
await EmployeeAsync("startsWith",               q => q.Where(e => e.FirstName.StartsWith("A")));
await EmployeeAsync("endsWith",                 q => q.Where(e => e.LastName.EndsWith("ov")));
await EmployeeAsync("contains",                 q => q.Where(e => e.Position.Contains("eng")));
await EmployeeAsync("containsIgnoreCase",       q => q.Where(e => e.Position.ToLower().Contains("eng")));
await EmployeeAsync("startsWithIgnoreCase",     q => q.Where(e => e.FirstName.ToLower().StartsWith("a")));
await EmployeeAsync("endsWithIgnoreCase",       q => q.Where(e => e.LastName.ToLower().EndsWith("ov")));
await EmployeeAsync("$and+$or",                 q => q.Where(e => e.Age > 18 && (e.Department == "IT" || e.Department == "HR")));
await EmployeeAsync("$or top-level",            q => q.Where(e => e.Department == "IT" || e.Department == "HR"));
await EmployeeAsync("$not",                     q => q.Where(e => !(e.Age < 18)));
await EmployeeAsync("$not + $and",              q => q.Where(e => !(e.Age < 18 && e.Department == "IT")));
await EmployeeAsync("multi-field AND merge",    q => q.Where(e => e.Department == "IT" && e.Age > 18));
await EmployeeAsync("date gt",                  q => q.Where(e => e.HireDate > new DateTime(2020, 1, 1)));
await EmployeeAsync("date lte",                 q => q.Where(e => e.HireDate <= new DateTime(2030, 1, 1)));
await EmployeeAsync("date range",               q => q.Where(e => e.HireDate >= new DateTime(2000, 1, 1) && e.HireDate < new DateTime(2030, 1, 1)));

// ---------------- Employee: base-field pushdown (WhereRedb) ------
await EmployeeAsync("base _id gt",              q => q.WhereRedb(o => o.Id > 0));
await EmployeeAsync("base 0$:Name startsWith",  q => q.WhereRedb(o => o.Name.StartsWith("E")));
await EmployeeAsync("pushdown _date_create gte",q => q.WhereRedb(o => o.DateCreate >= new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero)));
await EmployeeAsync("pushdown base+props AND",  q => q.WhereRedb(o => o.Id > 0).Where(e => e.FirstName.StartsWith("A")));
await EmployeeAsync("pushdown $or all-base",    q => q.WhereRedb(o => o.Id < 10 || o.Id > 100));
await EmployeeAsync("pushdown $not base",       q => q.WhereRedb(o => !(o.Id < 0)));
await EmployeeAsync("pushdown nested $and+$or", q => q.WhereRedb(o => o.Id > 0).Where(e => e.Department == "IT" || e.Department == "HR"));

// ---------------- Employee: expression engine (B2) ---------------
await EmployeeAsync("expr arithmetic gt",       q => q.Where(e => e.Age + 1 > 18));
await EmployeeAsync("expr mul base pushdown",   q => q.WhereRedb(o => o.Id * 2 < 1_000_000));
await EmployeeAsync("expr upper eq",            q => q.Where(e => e.FirstName.ToUpper() == "IVAN"));
await EmployeeAsync("expr concat ilike",        q => q.Where(e => (e.FirstName + " " + e.LastName).ToLower().Contains("van")));
await EmployeeAsync("expr coalesce eq",         q => q.Where(e => (e.EmployeeCode ?? "none") == "IT"));
await EmployeeAsync("expr length gt",           q => q.Where(e => e.FirstName.Length > 0));
await EmployeeAsync("expr abs sub",             q => q.Where(e => Math.Abs(e.Age - 30) < 1000));
await EmployeeAsync("expr salary * 12",         q => q.Where(e => e.Salary * 12m > 1_000_000m));

// ---------------- Employee: arrays -------------------------------
await EmployeeAsync("array contains",           q => q.Where(e => e.Skills!.Contains("C#")));
await EmployeeAsync("array length gt",          q => q.Where(e => e.Skills!.Length > 0));
await EmployeeAsync("array length eq 3",        q => q.Where(e => e.Skills!.Length == 3));
await EmployeeAsync("array any",                q => q.Where(e => e.Skills!.Any()));
await EmployeeAsync("array not any",            q => q.Where(e => !e.Skills!.Any()));
await EmployeeAsync("array int contains 5",     q => q.Where(e => e.SkillLevels!.Contains(5)));

// ---------------- Employee: dictionaries -------------------------
await EmployeeAsync("dict ContainsKey work",    q => q.Where(e => e.PhoneDirectory!.ContainsKey("work")));
await EmployeeAsync("dict indexer eq",          q => q.Where(e => e.PhoneDirectory!["work"] == "+7-000-0000000"));
await EmployeeAsync("dict nested .City",        q => q.Where(e => e.OfficeLocations!["Moscow"].City == "Moscow"));
await EmployeeAsync("dict nested .Street!=null",q => q.Where(e => e.OfficeLocations!["Moscow"].Street != null));

// ---------------- Person: ListItem accessors ---------------------
await PersonAsync("Person eq Name",             q => q.Where(p => p.Name == "Alice"));
await PersonAsync("Person Age range",           q => q.Where(p => p.Age >= 18 && p.Age <= 99));
await PersonAsync("Person Email contains @",    q => q.Where(p => p.Email.Contains("@")));
await PersonAsync("Person Status.Value eq",     q => q.Where(p => p.Status!.Value == "Active"));
await PersonAsync("Person Roles[].Value any",   q => q.Where(p => p.Roles!.Any(r => r.Value == "admin")));

// ---------------- City -------------------------------------------
await CityAsync("City bool true",               q => q.Where(c => c.IsCapital == true));
await CityAsync("City bool false",              q => q.Where(c => c.IsCapital == false));
await CityAsync("City coords length eq 2",      q => q.Where(c => c.Coordinates.Length == 2));
await CityAsync("City population gt",           q => q.Where(c => c.Population > 1000));
await CityAsync("City region startsWith M",     q => q.Where(c => c.Region.StartsWith("M")));
await CityAsync("City name in",                 q => q.Where(c => new[] { "Moscow", "Paris" }.Contains(c.Name)));

// ---------------- Department -------------------------------------
await DepartmentAsync("Dept eq bool",           q => q.Where(d => d.IsActive == true));
await DepartmentAsync("Dept name startsWith",   q => q.Where(d => d.Name.StartsWith("IT")));
await DepartmentAsync("Dept code contains '-'", q => q.Where(d => d.Code.Contains("-")));
await DepartmentAsync("Dept budget >= 0",       q => q.Where(d => d.Budget >= 0m));
await DepartmentAsync("Dept description != null", q => q.Where(d => d.Description != null));

// ---------------- Write SQL log to disk --------------------------
string? outPath = null;
try
{
    var dir = Path.Combine(AppContext.BaseDirectory, "Output");
    Directory.CreateDirectory(dir);
    outPath = Path.Combine(dir, "smoke_sql_dump.txt");
    await File.WriteAllTextAsync(outPath, log.ToString());
}
catch (Exception ex)
{
    Console.WriteLine($"[WARN] failed to write dump file: {ex.Message}");
}

sw.Stop();
SqlDebugSmokeProPvtIRedbQueryable.ToSqlStringAsync
PG Pro 84ms(65)
postgres.pro.repeat 19ms(65)
postgres.pro.cache 20ms(65)
MS Pro 133ms(65)
mssql.pro.repeat 21ms(65)
mssql.pro.cache 15ms(65)
sqlite.pro 57ms(65)
sqlite.pro.repeat 16ms(65)
sqlite.pro.cache 18ms(65)