Skip to content

Repository Pattern Guide

The repository pattern provides typed entity storage with schema validation, automatic timestamps, and versioning.


Repositories offer:

  • Type Safety: Work with strongly-typed entities
  • Schema Validation: Ensure data consistency
  • Auto-Generated IDs: Unique identifiers for each entity
  • Automatic Timestamps: createdAt and updatedAt fields
  • Versioning: Built-in optimistic concurrency

Use CaseRepositoryRaw Items
Domain entities with defined structureYes-
Schema validation requiredYes-
Dynamic/flexible data-Yes
Single-table design patterns-Yes
Type-safe SDKsYes-

Extend EntityBase for your entities:

using TerraScale.Database.Client.Abstractions;
public record Customer : EntityBase
{
public required string Name { get; init; }
public required string Email { get; init; }
public string? Phone { get; init; }
public CustomerTier Tier { get; init; } = CustomerTier.Standard;
public List<string> Tags { get; init; } = new();
}
public enum CustomerTier
{
Standard,
Premium,
Enterprise
}

EntityBase provides these properties automatically:

public abstract record EntityBase : IEntity
{
public required string Id { get; init; }
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
public DateTimeOffset? UpdatedAt { get; init; }
public long Version { get; init; } = 1;
public virtual string GetPartitionKey() => Id;
public virtual string? GetSortKey() => null;
}

Override key methods for custom partitioning:

public record Order : EntityBase
{
public required string CustomerId { get; init; }
public required string OrderNumber { get; init; }
public decimal Total { get; init; }
// Use customer as partition key
public override string GetPartitionKey() => $"customer#{CustomerId}";
// Use order number as sort key
public override string? GetSortKey() => $"order#{OrderNumber}";
}

var customers = client.GetRepository<Customer>("repo_customers");
var customer = new Customer
{
Id = Guid.NewGuid().ToString(),
Name = "Acme Corp",
Email = "contact@acme.com",
Tier = CustomerTier.Premium
};
var result = await customers.CreateAsync(customer);
if (result.IsSuccess)
{
Console.WriteLine($"Created customer with ID: {result.Value.Id}");
}
var result = await customers.GetAsync(customerId);
if (result.IsSuccess)
{
var customer = result.Value;
Console.WriteLine($"Customer: {customer.Name}");
}
// Get existing entity
var getResult = await customers.GetAsync(customerId);
var customer = getResult.Value;
// Create updated version
var updated = customer with
{
Tier = CustomerTier.Enterprise,
Tags = customer.Tags.Append("upgraded").ToList()
};
// Save changes
var updateResult = await customers.UpdateAsync(updated);
var result = await customers.DeleteAsync(customerId);
if (result.IsSuccess)
{
Console.WriteLine("Customer deleted");
}
var result = await customers.ListAsync(new PaginationOptions
{
Limit = 50
});
if (result.IsSuccess)
{
foreach (var customer in result.Value.Items)
{
Console.WriteLine($"{customer.Name} ({customer.Tier})");
}
// Handle pagination
if (result.Value.HasMore)
{
var nextPage = await customers.ListAsync(new PaginationOptions
{
Limit = 50,
NextToken = result.Value.NextToken
});
}
}
var filter = new QueryFilter
{
PartitionKey = $"customer#{customerId}",
SortKeyCondition = SortKeyCondition.BeginsWith("order#")
};
var result = await customers.QueryAsync(filter);
var result = await customers.ExistsAsync(customerId);
if (result.IsSuccess && result.Value)
{
Console.WriteLine("Customer exists");
}

using TerraScale.Database.Client;
using TerraScale.Database.Client.Abstractions;
using TerraScale.Database.Client.Configuration;
// Define entity
public record Product : EntityBase
{
public required string Name { get; init; }
public required string Category { get; init; }
public decimal Price { get; init; }
public bool InStock { get; init; } = true;
public List<string> Tags { get; init; } = new();
}
// Create client
var client = new TerraScaleDatabase(new TerraScaleDatabaseOptions
{
ApiKey = "ts_live_your_api_key",
Endpoint = "https://api.terrascale.io",
DefaultDatabase = "my-database"
});
// Get repository
var products = client.GetRepository<Product>("repo_products");
// Create product
var product = new Product
{
Id = "prod_001",
Name = "Wireless Mouse",
Category = "Electronics",
Price = 29.99m,
Tags = new() { "computer", "accessories" }
};
await products.CreateAsync(product);
// Update price
var updated = product with { Price = 24.99m };
await products.UpdateAsync(updated);
// List all products
var allProducts = await products.ListAsync(new PaginationOptions { Limit = 100 });
foreach (var p in allProducts.Value.Items)
{
Console.WriteLine($"{p.Name}: ${p.Price}");
}
// Clean up
await client.DisposeAsync();

When creating a repository via the Management API, define a schema:

{
"name": "products",
"entityName": "Product",
"schema": {
"name": { "type": "string", "required": true, "minLength": 1 },
"category": { "type": "string", "required": true },
"price": { "type": "number", "required": true, "min": 0 },
"inStock": { "type": "boolean", "required": false },
"tags": { "type": "array", "required": false, "items": { "type": "string" } }
}
}

Entities that don’t match the schema will be rejected.


C# records with init properties ensure immutability:

public record Customer : EntityBase
{
public required string Name { get; init; } // Immutable after creation
}
// Update by creating new instance
var updated = customer with { Name = "New Name" };

Use the Version property to detect conflicts:

try
{
await products.UpdateAsync(product);
}
catch (ConcurrencyException)
{
// Another process updated the entity
var fresh = await products.GetAsync(product.Id);
// Merge changes and retry
}

Override key methods to optimize queries:

// If you often query by customer
public override string GetPartitionKey() => $"customer#{CustomerId}";