Transparent horizontal sharding and temporal versioning for Entity Framework Core
π Documentation Β· π Quick Start Β· π‘ Samples Β· π Benchmarks
DTDE is a NuGet package that adds transparent horizontal sharding and optional temporal versioning to Entity Framework Core applications. Write standard LINQ queries β DTDE handles data distribution, query routing, and result merging automatically.
// Standard EF Core LINQ - DTDE routes queries transparently
var euCustomers = await db.Customers
.Where(c => c.Region == "EU")
.ToListAsync(); // Automatically queries only EU shard
// Point-in-time queries for temporal entities
var ordersLastMonth = await db.ValidAt<Order>(DateTime.Today.AddMonths(-1))
.Where(o => o.Status == "Completed")
.ToListAsync();| Feature | Description |
|---|---|
| π Transparent Sharding | Distribute data across tables or databases invisibly |
| β±οΈ Temporal Versioning | Track entity history with point-in-time queries |
| π Cross-Shard Transactions | ACID transactions spanning multiple database shards |
| π― Property Agnostic | Use ANY property names for sharding keys and temporal boundaries |
| π EF Core Native | Works with standard LINQ β no special query syntax required |
| β‘ Multiple Strategies | Date-based, hash-based, range-based, or composite sharding |
| ποΈ Hot/Warm/Cold Tiers | Support for data tiering across storage tiers |
| β Fully Tested | 400+ unit and integration tests |
# All-in-one package (recommended)
dotnet add package Dtde.EntityFramework
# Or install individual packages
dotnet add package Dtde.Abstractions # Core interfaces
dotnet add package Dtde.Core # Core implementations
dotnet add package Dtde.EntityFramework # EF Core integrationRequirements: .NET 8.0+ / 9.0+ / 10.0+, Entity Framework Core 8.0+ / 9.0+ / 10.0+
public class Customer
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Region { get; set; } = string.Empty; // Shard key
public DateTime CreatedAt { get; set; }
}public class AppDbContext : DtdeDbContext
{
public DbSet<Customer> Customers => Set<Customer>();
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Customer>(entity =>
{
entity.ShardBy(c => c.Region); // Enable sharding by Region
});
}
}builder.Services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlServer(connectionString);
options.UseDtde(dtde =>
{
dtde.AddShard(s => s.WithId("EU").WithShardKeyValue("EU").WithTier(ShardTier.Hot));
dtde.AddShard(s => s.WithId("US").WithShardKeyValue("US").WithTier(ShardTier.Hot));
dtde.AddShard(s => s.WithId("APAC").WithShardKeyValue("APAC").WithTier(ShardTier.Hot));
});
});// Queries are automatically routed to correct shard(s)
var allCustomers = await _context.Customers.ToListAsync(); // Queries all shards
var euCustomers = await _context.Customers
.Where(c => c.Region == "EU")
.ToListAsync(); // Only queries EU shard
// Inserts are automatically routed based on shard key
_context.Customers.Add(new Customer { Name = "Acme Corp", Region = "US" }); // Routes to US shard
await _context.SaveChangesAsync();DTDE supports multiple sharding strategies to match your data access patterns:
Distribute data by a property value (region, tenant, category):
modelBuilder.Entity<Customer>(entity =>
{
entity.ShardBy(c => c.Region);
});Use cases: Multi-region deployments, GDPR compliance, data residency
Partition data by date for time-series workloads:
modelBuilder.Entity<Transaction>(entity =>
{
entity.ShardByDate(t => t.TransactionDate, DateInterval.Month);
});Use cases: Financial transactions, audit logs, metrics, event sourcing
Even distribution across shards using consistent hashing:
modelBuilder.Entity<UserProfile>(entity =>
{
entity.ShardByHash(u => u.UserId, shardCount: 8);
});Use cases: High-volume data, preventing hotspots, horizontal scaling
Combine strategies for complex scenarios:
modelBuilder.Entity<Order>(entity =>
{
entity.ShardBy(o => o.Region)
.ThenByDate(o => o.OrderDate);
});Track entity history and query data at any point in time:
modelBuilder.Entity<Contract>(entity =>
{
entity.HasTemporalValidity(
validFrom: nameof(Contract.ValidFrom),
validTo: nameof(Contract.ValidTo));
});// Current data
var current = await _context.ValidAt<Contract>(DateTime.UtcNow).ToListAsync();
// Historical data (as of a specific date)
var historical = await _context.ValidAt<Contract>(new DateTime(2024, 1, 1)).ToListAsync();
// All versions (bypass temporal filtering)
var allVersions = await _context.AllVersions<Contract>()
.Where(c => c.ContractNumber == "C-001")
.OrderBy(c => c.ValidFrom)
.ToListAsync();
// Data within a date range
var rangeData = await _context.ValidBetween<Contract>(startDate, endDate).ToListAsync();// Add with effective date
_context.AddTemporal(contract, effectiveFrom: DateTime.UtcNow);
// Create new version (closes old, opens new)
var newVersion = _context.CreateNewVersion(existing, changes, effectiveDate);
// Terminate (close validity)
_context.Terminate(contract, terminationDate: DateTime.UtcNow);
await _context.SaveChangesAsync();DTDE works seamlessly alongside regular EF Core entities:
public class AppDbContext : DtdeDbContext
{
public DbSet<Customer> Customers => Set<Customer>(); // Sharded
public DbSet<Contract> Contracts => Set<Contract>(); // Temporal
public DbSet<AuditLog> AuditLogs => Set<AuditLog>(); // Regular EF Core
}| Entity Configuration | Behavior |
|---|---|
ShardBy() configured |
Queries routed by shard key |
HasTemporalValidity() configured |
Temporal filtering applied |
| No special configuration | Standard EF Core entity |
Direct DbSet<T> access |
Bypasses DTDE filtering |
Organize shards by access frequency for cost optimization:
dtde.AddShard(s => s
.WithId("2024-current")
.WithTier(ShardTier.Hot) // Frequently accessed, recent data
.WithConnectionString(fastStorage));
dtde.AddShard(s => s
.WithId("2023-archive")
.WithTier(ShardTier.Cold) // Archived, rarely accessed
.WithConnectionString(cheapStorage)
.AsReadOnly()); // Prevent accidental writes| Tier | Description | Typical Storage |
|---|---|---|
Hot |
Frequently accessed, recent data | SSD, Premium SQL |
Warm |
Less frequently accessed | Standard SQL |
Cold |
Archived, rarely accessed | Archive storage |
Archive |
Long-term retention | Cold storage |
options.UseDtde(dtde =>
{
// Entity configuration
dtde.ConfigureEntity<Order>(e => e.ShardByDate(o => o.OrderDate));
// Shard definitions
dtde.AddShard(s => s.WithId("primary").WithConnectionString("..."));
// Load from configuration file
dtde.AddShardsFromConfig("shards.json");
// Default temporal context
dtde.SetDefaultTemporalContext(() => DateTime.UtcNow);
// Performance tuning
dtde.SetMaxParallelShards(10);
// Diagnostics
dtde.EnableDiagnostics();
});{
"shards": [
{
"shardId": "2024-q4",
"name": "Q4 2024 Data",
"connectionString": "Server=...;Database=Data2024Q4",
"tier": "Hot",
"dateRangeStart": "2024-10-01",
"dateRangeEnd": "2024-12-31",
"isReadOnly": false,
"priority": 100
},
{
"shardId": "2024-archive",
"name": "2024 Archive",
"connectionString": "Server=...;Database=Archive2024",
"tier": "Cold",
"isReadOnly": true,
"priority": 10
}
]
}Comprehensive benchmarks comparing single table vs sharded approaches:
| Component | Specification |
|---|---|
| OS | Windows 11 (10.0.26200.7462) |
| CPU | 12th Gen Intel Core i9-12900H (14 cores, 20 threads) |
| Runtime | .NET 9.0.10, RyuJIT AVX2 |
| SDK | .NET SDK 10.0.101 |
| Database | SQLite (file-based, separate DBs per benchmark) |
| Framework | BenchmarkDotNet v0.15.0 |
| Query Type | Records | Single Table | Sharded | Improvement |
|---|---|---|---|---|
| Point Lookup (by ID) | 100K | 153.1 ns | 145.7 ns | ~Same |
| Date Range (1 month) | 100K | 16,257 Β΅s | 3,555 Β΅s | 4.6x faster |
| Region Scan (single) | 100K | 3,638 Β΅s | 1,774 Β΅s | 2.1x faster |
| Region Scan (all) | 100K | 5,535 Β΅s | 2,014 Β΅s | 2.7x faster |
| Count (all records) | 100K | 7,873 Β΅s | 1,330 Β΅s | 5.9x faster |
| Count (all records) | 50K | 3,387 Β΅s | 24.1 Β΅s | 141x faster |
| Scenario | Threads | Single Table | Sharded | Improvement |
|---|---|---|---|---|
| Parallel Aggregation | 4 | 1,668 Β΅s | 546 Β΅s | 3.1x faster |
| Parallel Aggregation | 8 | 2,043 Β΅s | 696 Β΅s | 2.9x faster |
| Parallel Reads (diff regions) | 4 | 1,822 Β΅s | 542 Β΅s | 3.4x faster |
| Parallel Reads (diff regions) | 8 | 2,479 Β΅s | 758 Β΅s | 3.3x faster |
| Operation | Batch Size | Single Table | Sharded | Notes |
|---|---|---|---|---|
| Batch Update | 100 | 414.5 Β΅s | 317.7 Β΅s | 23% faster |
| Batch Update | 1000 | 388.8 Β΅s | 299.5 Β΅s | 23% faster |
| Batch Delete | 100 | 326.8 Β΅s | 302.4 Β΅s | ~Same |
Key insight: Sharded queries benefit significantly from partition pruning β queries that target a specific shard key value only scan relevant partitions. Concurrent workloads show even greater improvements due to reduced contention.
β Good candidates:
- Large datasets (millions+ rows)
- Time-series / temporal data
- Multi-tenant applications
- Geographic distribution requirements
- High write throughput needs
- Hot/cold data patterns
- Concurrent read-heavy workloads
- Small datasets (<100K rows)
- Random access patterns
- Complex cross-entity joins
- Simple CRUD applications
# Run benchmarks
cd benchmarks/Dtde.Benchmarks
dotnet run -c Releaseβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Your Application β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Dtde.EntityFramework β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β βDtdeDbContextβ βQuery Engine β β Update Engine β β
β β - ValidAt β β - Rewriter β β - VersionManager β β
β β - AllVersionsβ β - Executor β β - ShardRouter β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Dtde.Core β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
β β Metadata β β Sharding β β Temporal β β
β β Registry β β Strategies β β Context β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Dtde.Abstractions β
β Interfaces β’ Contracts β’ Exceptions β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
dtde/
βββ src/
β βββ Dtde.Abstractions/ # Core interfaces and contracts
β βββ Dtde.Core/ # Core implementations
β βββ Dtde.EntityFramework/ # EF Core integration
βββ tests/
β βββ Dtde.Core.Tests/ # Unit tests
β βββ Dtde.EntityFramework.Tests/
β βββ Dtde.Integration.Tests/
βββ samples/ # Sample applications
β βββ Dtde.Sample.WebApi/ # Basic getting started
β βββ Dtde.Samples.RegionSharding/ # Property-based sharding
β βββ Dtde.Samples.DateSharding/ # Date-based sharding
β βββ Dtde.Samples.HashSharding/ # Hash-based sharding
β βββ Dtde.Samples.MultiTenant/ # Multi-tenancy
β βββ Dtde.Samples.Combined/ # Mixed strategies
βββ benchmarks/
β βββ Dtde.Benchmarks/ # Performance benchmarks
βββ docs/ # Documentation (MkDocs)
Explore working examples for each sharding strategy:
| Sample | Strategy | Use Case |
|---|---|---|
| Dtde.Sample.WebApi | Basic | Getting started |
| Dtde.Samples.RegionSharding | Property-based | Multi-region data residency |
| Dtde.Samples.DateSharding | Date-based | Time-series, audit logs |
| Dtde.Samples.HashSharding | Hash-based | Even data distribution |
| Dtde.Samples.MultiTenant | Tenant-based | SaaS multi-tenancy |
| Dtde.Samples.Combined | Mixed | Complex enterprise scenarios |
# Run all tests (101 tests)
dotnet test
# Run with coverage
dotnet test --collect:"XPlat Code Coverage"
# Run specific test project
dotnet test tests/Dtde.Core.Tests/- Full Documentation β Complete guides, API reference, and tutorials
- Quick Start Guide β Get running in 5 minutes
- Sharding Guide β Deep dive into sharding strategies
- Temporal Guide β Temporal versioning explained
- API Reference β Complete API documentation
- Architecture β Design decisions and architecture
Contributions are welcome! Please read our Contributing Guide before submitting PRs.
# Clone and build
git clone https://github.com/yohasacura/dtde.git
cd dtde
dotnet build
dotnet testSee also:
This project is licensed under the MIT License β see the LICENSE file for details.
- Built with Entity Framework Core
- Inspired by temporal database concepts and bi-temporal data modeling
- Benchmarked with BenchmarkDotNet
Made with β€οΈ for the .NET community