Your data has hierarchy. Then different entity types appear at different levels. Now what?
Imagine a large corporation. Multiple headquarters. Regional offices under each HQ.
Departments in every office. Warehouses under departments. Retail points. Showrooms.
Products in warehouses. And then... a product gets transferred to HQ for review.
Wait — the product was in a warehouse, but now it's in... a showroom? At headquarters?
And tomorrow it might be at a retail point in another city?
How do you model THIS in your database?
Traditional
-- The "Enterprise Hierarchy" nightmare
--
-- First, tables for each level:
CREATE TABLE Headquarters (id, name, ...);
CREATE TABLE RegionalOffices (id, hq_id, name, ...);
CREATE TABLE Departments (id, office_id, name, ...);
CREATE TABLE Warehouses (id, department_id, ...);
CREATE TABLE RetailPoints (id, department_id, ...);
CREATE TABLE Showrooms (id, office_id, ...);
CREATE TABLE Products (id, name, sku, ...);
-- Now the fun part: WHERE is the product?
-- Option A: Multiple FK columns
ALTER TABLE Products ADD warehouse_id INT NULL;
ALTER TABLE Products ADD retail_point_id INT NULL;
ALTER TABLE Products ADD showroom_id INT NULL;
ALTER TABLE Products ADD hq_showroom_id INT NULL;
-- Only ONE should be non-null. Good luck enforcing that.
-- Option B: Polymorphic association (the "Rails way")
ALTER TABLE Products ADD location_type VARCHAR(50);
ALTER TABLE Products ADD location_id INT;
-- No FK constraint possible. Orphaned records guaranteed.
-- Option C: Junction tables for each combination
CREATE TABLE Product_Warehouse (product_id, warehouse_id);
CREATE TABLE Product_RetailPoint (product_id, point_id);
CREATE TABLE Product_Showroom (product_id, showroom_id);
-- Now track history of movements...
CREATE TABLE ProductMovementHistory (...);
-- Query: "All products under London HQ (any level)"
-- 250 lines of recursive CTEs across 7 tables
-- Performance: 3-5 seconds on 100K products
-- Move product to different location?
-- BEGIN TRANSACTION;
-- DELETE FROM Product_Warehouse WHERE...
-- INSERT INTO Product_Showroom VALUES...
-- INSERT INTO ProductMovementHistory...
-- COMMIT;
-- Hope you got the order right!
RedBase
// Define your entities - just C# classes with attribute
[RedbScheme] public class Headquarters { ... }
[RedbScheme] public class Office { ... }
[RedbScheme] public class Department { ... }
[RedbScheme] public class Warehouse { ... }
[RedbScheme] public class Showroom { ... }
[RedbScheme] public class Product { ... }
// Create hierarchy - any entity under any parent
await redb.CreateChildAsync(product, warehouse);
// Product is now under Warehouse. Done.
// Move product to HQ showroom? One line:
await redb.MoveObjectAsync(product, hqShowroom);
// History tracked automatically. Done.
// Find ALL products under London HQ (any depth)
var products = await redb
.TreeQuery<Product>(londonHQ.Id, maxDepth: 10)
.Where(p => p.InStock)
.ToListAsync();
// All products from all warehouses, showrooms,
// retail points under London. 15ms.
// Find all departments at level 3
var level3 = await redb
.TreeQuery<Department>(hq.Id)
.WhereLevel(3)
.ToListAsync();
// Build complete tree with all children loaded
var tree = await redb
.LoadTreeAsync<Headquarters>(hq, maxDepth: 5);
// Navigate: tree.Children[0].Children[1].Props
The secret: In RedBase, hierarchy is built-in. Every object has a parent.
Different entity types can live at any level. A Product can be under a Warehouse today
and under a Showroom tomorrow — same API, same table, zero schema changes.
But wait, it gets worse...
The MacBook is on a warehouse shelf. Before shipping to HQ for executive review,
someone puts an accessory kit inside the box. A cable, a case, maybe AirPods — for the boss.
Now the box travels to HQ. At HQ, they might take out the accessory and keep it there.
Or the whole package goes to retail — MacBook with accessory still attached.
In traditional DB: How do you model "accessory inside a box inside a warehouse"?
Then move the box? Then optionally detach the accessory?
I smell bugs. Lots of them.
Traditional
-- Accessory inside box? More junction tables!
CREATE TABLE ProductContents (
container_id INT,
content_id INT,
added_date DATETIME,
-- Wait, what if content has its own contents?
-- Recursive nightmare begins...
);
-- Move box to HQ
UPDATE Products SET location_id = @hqId
WHERE id = @boxId;
-- OOPS! Accessory still points to warehouse!
-- Manual fix required:
UPDATE Products SET location_id = @hqId
WHERE id IN (SELECT content_id FROM ProductContents
WHERE container_id = @boxId);
-- What about nested contents? More recursion...
-- Detach accessory at HQ?
DELETE FROM ProductContents
WHERE container_id = @boxId AND content_id = @accId;
UPDATE Products SET location_id = @hqId
WHERE id = @accId;
-- History? Audit trail? Good luck.
RedBase
// Put accessory inside MacBook box
await redb.CreateChildAsync(accessory, macbookBox);
// Done. Accessory is now INSIDE the box.
// Move entire box to HQ
await redb.MoveObjectAsync(macbookBox, hqShowroom);
// Accessory travels WITH the box. Automatically.
// No extra code. No forgotten updates.
// Take accessory out, leave at HQ
await redb.MoveObjectAsync(accessory, hqShowroom);
// Accessory now directly under HQ.
// MacBook box can go to retail alone.
// Query: What's inside any container?
var contents = await redb
.TreeQuery<Product>(macbookBox.Id)
.ToListAsync();
// Full audit: where was accessory?
var path = await redb
.GetPathToRootAsync<Product>(accessory);
// [Accessory] → [Box] → [Showroom] → [HQ]
The point: Parent-child relationships are automatic.
Move a parent — children follow. Detach a child — it stays where you put it.
Query any level. No orphans. No forgotten updates. No bugs at 2 AM.
Keep growing. Keep nesting. Still just a few lines.
Tomorrow the business says: "We need to track which employee packed each box."
Add a property. Done.
Next week: "Boxes can contain other boxes."
Already works. Any object can be a parent.
Next month: "We need shipment containers that hold multiple boxes from different warehouses,
going to different destinations, with customs documents attached."
CreateChildAsync. MoveObjectAsync. TreeQuery. Same API. Zero migrations.
Your competitors are still writing ALTER TABLE scripts. You're shipping features.
But wait... objects don't just have parents. They reference OTHER objects.
An Order references a Customer. And multiple Products. And a Shipping address.
And Payment history. And Discount coupons. And a Sales manager who made the deal.
Oh, and the Customer has their own Address. And a list of previous Orders (circular!).
And the Products have Categories. And Suppliers. And Reviews from other Customers.
Now save all of this. In the right order. With correct FKs.
Without orphans. Without deadlocks. Welcome to Graph Hell.
Traditional
-- Order references Customer, Products, Payments...
CREATE TABLE Orders (
id INT PRIMARY KEY,
customer_id INT REFERENCES Customers(id),
shipping_address_id INT REFERENCES Addresses(id),
sales_manager_id INT REFERENCES Employees(id),
discount_id INT NULL REFERENCES Discounts(id),
-- Wait, what about multiple products?
);
-- Junction table for Order-Product many-to-many
CREATE TABLE OrderProducts (
order_id INT REFERENCES Orders(id),
product_id INT REFERENCES Products(id),
quantity INT,
price_at_order DECIMAL,
PRIMARY KEY (order_id, product_id)
);
-- Another junction for payment history
CREATE TABLE OrderPayments (
order_id INT REFERENCES Orders(id),
payment_id INT REFERENCES Payments(id),
-- ...
);
-- Save order? Here's the nightmare:
BEGIN TRANSACTION;
-- 1. Ensure Customer exists (or create)
-- 2. Ensure Address exists (or create)
-- 3. INSERT Order (need customer_id first!)
-- 4. INSERT OrderProducts for each product
-- 5. INSERT Payments
-- 6. INSERT OrderPayments links
-- 7. UPDATE Order with payment totals
-- Hope nothing failed in between!
COMMIT;
-- Delete order? CASCADE horror awaits.
-- Load order with all relations? 15 JOINs.
RedBase
// Just declare your business model
[RedbScheme]
public class OrderProps
{
public Customer Customer { get; set; }
public Address ShippingAddress { get; set; }
public Employee SalesManager { get; set; }
// Array of objects? Just declare it.
public Product[] Products { get; set; }
// Array of RedbObjects with their own Props!
public RedbObject<PaymentProps>[] Payments { get; set; }
// Dictionary of objects? Why not!
public Dictionary<string, RedbObject<CouponProps>>
Coupons { get; set; }
}
// Save entire object graph
var order = new RedbObject<OrderProps>
{
Props = new OrderProps
{
Customer = customer,
Products = new[] { laptop, mouse, keyboard },
Payments = new[] { payment1, payment2 },
Coupons = new Dictionary<string, ...>
{
["SUMMER20"] = summerDiscount
}
}
};
await redb.SaveAsync(order);
// ALL nested objects saved. All links created.
// One line. One transaction. Done.
// Load with full graph
var loaded = await redb.LoadAsync<OrderProps>(id);
// loaded.Props.Customer - ready
// loaded.Props.Products[0] - ready
// loaded.Props.Payments[1].Props - ready
// loaded.Props.Coupons["SUMMER20"] - ready
Object graphs are first-class citizens.
Declare references, arrays, dictionaries of objects in your Props.
Save once — the entire graph persists. Load once — the entire graph materializes.
No junction tables. No FK nightmares. No orphans. Ever.
Customer Customer
Single object reference
Product[] Products
Array of business classes
RedbObject<T>[] Items
Array of full objects with IDs
Dictionary<K, T>
Keyed collections of anything