Data Model - EmployeePropsClick 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; }
}
Select - Projection
Project specific fields using Select (server-side projection). Fetches only requested Props fields, reducing data transfer.
var sw = Stopwatch.StartNew();
// Server-side Distinct by Props hash
var query = redb.Query<EmployeeProps>()
.Distinct()
.Take(100);
// Uncomment to see generated SQL:
// var sql = await query.ToSqlStringAsync();
// Console.WriteLine(sql);
var uniqueEmployees = await query.ToListAsync();
// Compare with total count
var totalCount = await redb.Query<EmployeeProps>().CountAsync();
sw.Stop();
DistinctUniqueDedupeIRedbQueryable.Distinct
MS1684ms(100)
MS Pro70ms(100)
PG619ms(100)
PG Pro51ms(100)
OrderBy - Sort by Salary
Sort employees by salary using OrderBy. Get top 5 lowest and highest earners.
var sw = Stopwatch.StartNew();
// Ascending - lowest earners
var lowest = await redb.Query<EmployeeProps>()
.OrderBy(e => e.Salary)
.Take(5)
.ToListAsync();
// Descending - highest earners
var highest = await redb.Query<EmployeeProps>()
.OrderByDescending(e => e.Salary)
.Take(5)
.ToListAsync();
sw.Stop();
var lowestSalary = lowest.FirstOrDefault()?.Props.Salary ?? 0;
var highestSalary = highest.FirstOrDefault()?.Props.Salary ?? 0;
OrderBySort
MS232ms(10)
PG200ms(10)
OrderBy + ThenBy
Sort employees by multiple fields using ThenBy. Sort by department, then by salary descending.
var sw = Stopwatch.StartNew();
var query = redb.Query<EmployeeProps>()
.OrderBy(e => e.Department)
.ThenByDescending(e => e.Salary)
.Take(100);
// Uncomment to see generated SQL:
// var sql = await query.ToSqlStringAsync();
// Console.WriteLine(sql);
var result = await query.ToListAsync();
sw.Stop();
var first = result.FirstOrDefault();
var info = first != null ? $"{first.Props.Department}: ${first.Props.Salary:N0}" : "-";
OrderByThenBySort
MS388ms(100)
PG289ms(100)
PG Pro76ms(100)
Skip/Take - Pagination
Paginate employees using Skip and Take. Get page 2 with 10 items per page.
var sw = Stopwatch.StartNew();
const int pageSize = 10;
const int page = 2; // 0-based would skip 10
var query = redb.Query<EmployeeProps>()
.OrderBy(e => e.LastName)
.Skip((page - 1) * pageSize)
.Take(pageSize);
// Uncomment to see generated SQL:
// var sql = await query.ToSqlStringAsync();
// Console.WriteLine(sql);
var result = await query.ToListAsync();
sw.Stop();
SkipTakePagination
MS71ms(10)
MS Pro64ms(10)
PG562ms(10)
PG Pro46ms(10)
OrderByDescending - Reverse Sort
Sort employees in descending order using OrderByDescending. Get top earners and most experienced employees.
Cleanup existing tree data before E088/E089 tests. Run this before E088 or E089 to ensure clean state. Uses DeleteWithPurgeAsync for permanent removal.
await redb.SyncSchemeAsync<DepartmentProps>();
var sw = Stopwatch.StartNew();
var existing = await redb.TreeQuery<DepartmentProps>().ToListAsync();
var count = existing.Count;
if (count > 0)
{
await redb.DeleteWithPurgeAsync(existing.Select(e => e.Id).ToList(), batchSize: 50);
}
sw.Stop();
TreeCleanupDeleteWithPurgeAsyncPro
MS3ms(0)
MS Pro9127ms(202)
PG16771ms(202)
PG Pro16884ms(202)
Tree Create BULK - Fast
BULK create tree hierarchy (Pro feature) - FAST version. Creates ~100 tree nodes using pre-generated IDs + AddNewObjectsAsync. Compare with E089 (sequential CreateChildAsync). Structure:
1 root (TechCorp)
10 regional offices
50 departments (5 per office)
40 teams (4 per first 10 departments)
Optimization:
NextObjectIdBatchAsync - get all IDs in 1 query
AddNewObjectsAsync - bulk insert all nodes in 1 operation
await redb.SyncSchemeAsync<DepartmentProps>();
// Run E087 first to cleanup existing data
var totalNodes = 1 + OfficeCount + (OfficeCount * DeptPerOffice) + (TeamsForFirstDepts * TeamsPerDept);
// Measure bulk creation
var sw = Stopwatch.StartNew();
// 1. Get all IDs in ONE query
var ids = await redb.Context.NextObjectIdBatchAsync(totalNodes);
// 2. Create all objects with pre-assigned IDs and parent_id
var nodes = GenerateTree(ids);
// 3. Bulk insert ALL nodes in ONE operation
await redb.AddNewObjectsAsync(nodes.Cast<IRedbObject<DepartmentProps>>().ToList());
sw.Stop();
var rate = nodes.Count * 1000 / Math.Max(sw.ElapsedMilliseconds, 1);
GenerateTree
private static List<TreeRedbObject<DepartmentProps>> GenerateTree(long[] ids)
{
var nodes = new List<TreeRedbObject<DepartmentProps>>();
var idx = 0;
// Level 0: Root
var rootId = ids[idx++];
nodes.Add(CreateNode(rootId, null, "TechCorp", "CORP", "Headquarters", 50_000_000m));
// Level 1: Regional offices
var officeIds = new long[OfficeCount];
for (int i = 0; i < OfficeCount; i++)
{
officeIds[i] = ids[idx++];
var city = Cities[i % Cities.Length];
nodes.Add(CreateNode(officeIds[i], rootId, $"{city} Office", $"OFF-{i + 1:D2}", $"Regional office {city}", 5_000_000m - i * 200_000m));
}
// Level 2: Departments in each office
var deptIds = new List<long>();
for (int o = 0; o < OfficeCount; o++)
{
for (int d = 0; d < DeptPerOffice; d++)
{
var deptId = ids[idx++];
deptIds.Add(deptId);
var deptName = Departments[d % Departments.Length];
nodes.Add(CreateNode(deptId, officeIds[o], $"{deptName} {o + 1}-{d + 1}", $"DEPT-{o + 1:D2}-{d + 1:D2}", deptName, 1_000_000m - d * 100_000m));
}
}
// Level 3: Teams in first 10 departments
for (int d = 0; d < TeamsForFirstDepts && d < deptIds.Count; d++)
{
for (int t = 0; t < TeamsPerDept; t++)
{
var teamId = ids[idx++];
var teamName = Teams[t % Teams.Length];
nodes.Add(CreateNode(teamId, deptIds[d], $"{teamName} Team {d + 1}-{t + 1}", $"TEAM-{d + 1:D2}-{t + 1:D2}", teamName, 300_000m - t * 50_000m));
}
}
return nodes;
}
TreeBulkAddNewObjectsAsyncPro
MS116ms(101)
MS Pro244ms(101)
PG220ms(101)
PG Pro221ms(101)
Tree Create Sequential - Slow
Sequential create tree hierarchy (Pro feature) - SLOW version. Creates ~100 tree nodes using sequential CreateChildAsync. Compare with E088 (bulk AddNewObjectsAsync). Structure:
1 root (TechCorp)
10 regional offices
50 departments (5 per office)
40 teams (4 per first 10 departments)
Each CreateChildAsync = separate DB round-trip.
await redb.SyncSchemeAsync<DepartmentProps>();
// Run E087 first to cleanup existing data
// Measure sequential creation
var sw = Stopwatch.StartNew();
var count = await CreateTreeSequentially(redb);
sw.Stop();
var rate = count * 1000 / Math.Max(sw.ElapsedMilliseconds, 1);
CreateTreeSequentially
private static async Task<int> CreateTreeSequentially(IRedbService redb)
{
var count = 0;
// Level 0: Root
var root = CreateDept("TechCorp", "CORP", "Headquarters", 50_000_000m);
root.id = await redb.SaveAsync(root);
count++;
// Level 1: Regional offices
var offices = new TreeRedbObject<DepartmentProps>[OfficeCount];
for (int i = 0; i < OfficeCount; i++)
{
var city = Cities[i % Cities.Length];
offices[i] = CreateDept($"{city} Office", $"OFF-{i + 1:D2}", $"Regional office {city}", 5_000_000m - i * 200_000m);
offices[i].id = await redb.CreateChildAsync(offices[i], root);
count++;
}
// Level 2: Departments in each office
var depts = new List<TreeRedbObject<DepartmentProps>>();
for (int o = 0; o < OfficeCount; o++)
{
for (int d = 0; d < DeptPerOffice; d++)
{
var deptName = Departments[d % Departments.Length];
var dept = CreateDept($"{deptName} {o + 1}-{d + 1}", $"DEPT-{o + 1:D2}-{d + 1:D2}", deptName, 1_000_000m - d * 100_000m);
dept.id = await redb.CreateChildAsync(dept, offices[o]);
depts.Add(dept);
count++;
}
}
// Level 3: Teams in first 10 departments
for (int d = 0; d < TeamsForFirstDepts && d < depts.Count; d++)
{
for (int t = 0; t < TeamsPerDept; t++)
{
var teamName = Teams[t % Teams.Length];
var team = CreateDept($"{teamName} Team {d + 1}-{t + 1}", $"TEAM-{d + 1:D2}-{t + 1:D2}", teamName, 300_000m - t * 50_000m);
await redb.CreateChildAsync(team, depts[d]);
count++;
}
}
return count;
}
TreeCreateChildAsyncSequentialPro
MS7038ms(101)
MS Pro7786ms(101)
PG7074ms(101)
PG Pro10190ms(101)
Tree Load - Full Hierarchy
Load full tree with LoadTreeAsync. Requires E089 to run first (creates tree data). Loads entire hierarchy from root with specified depth.