PartiTables 1.0.3

dotnet add package PartiTables --version 1.0.3
                    
NuGet\Install-Package PartiTables -Version 1.0.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PartiTables" Version="1.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PartiTables" Version="1.0.3" />
                    
Directory.Packages.props
<PackageReference Include="PartiTables" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PartiTables --version 1.0.3
                    
#r "nuget: PartiTables, 1.0.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PartiTables@1.0.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PartiTables&version=1.0.3
                    
Install as a Cake Addin
#tool nuget:?package=PartiTables&version=1.0.3
                    
Install as a Cake Tool

PartiTables

Azure Table Storage made simple for .NET

NuGet License: MIT

๐Ÿง  Why PartiTables?

Azure Table Storage is 95% cheaper than other NoSQL solutions and blazing fast โ€” but painful to use. PartiTables fixes that by providing clean, Entity Framework-style data access patterns.

Save Money - $10/month instead of $240/month
Type-Safe - IntelliSense, compile-time checking
Auto-Retry - Built-in resilience with Polly
Batch Operations - Save multiple entities atomically
Less Code - One class replaces hundreds of lines


๐Ÿ“ฆ Installation

dotnet add package PartiTables

๐Ÿš€ Quick Start

Before vs After

Without PartiTables:

// The old way: manual TableEntity manipulation
var entity = new TableEntity("partition", "row-key");
entity["FirstName"] = "John";
entity["LastName"] = "Doe";
await tableClient.UpsertEntityAsync(entity);
// ... tedious manual parsing when reading
// ... complex batch operations
// ... manual retry logic

With PartiTables:

// Use them like Entity Framework
var patient = new Patient { PatientId = "patient-123" };
patient.Meta.Add(new PatientMeta { FirstName = "John", Email = "john@example.com" });
patient.Consents.Add(new Consent { Type = "DataSharing" });

await repo.SaveAsync(patient);
//   โœ… All related records saved in ONE batch operation
//   โœ… Automatic retry with Polly resilience
//   โœ… RowKeys auto-generated from patterns
//   โœ… Strong typing, IntelliSense, compile-time safety

๐Ÿ’ก Key Features

Define Your Model with Declarative Patterns

[TablePartition("Customers", "{CustomerId}")]
public class Customer
{
    public string CustomerId { get; set; }
    
    [RowKeyPrefix("")]
    public List<Order> Orders { get; set; } = new();
    
    [RowKeyPrefix("")]
    public List<Address> Addresses { get; set; } = new();
}

[RowKeyPattern("{CustomerId}-order-{OrderId}")]
public class Order : RowEntity
{
    public string OrderId { get; set; } = Guid.NewGuid().ToString("N")[..8];
    public decimal Amount { get; set; }
    public string Status { get; set; } = "Pending";
}

[RowKeyPattern("{CustomerId}-address-{AddressId}")]
public class Address : RowEntity
{
    public string AddressId { get; set; } = Guid.NewGuid().ToString("N")[..8];
    public string City { get; set; } = default!;
}

Benefits:

  • โœ… Self-documenting - pattern visible at a glance
  • โœ… 60% less code than manual implementation
  • โœ… Automatic key generation from properties
  • โœ… Type-safe and compile-time validated

CRUD Operations

// Create
var customer = new Customer { CustomerId = "cust-123" };
customer.Orders.Add(new Order { Amount = 99.99m, Status = "Pending" });
await repo.SaveAsync(customer);

// Read
var customer = await repo.FindAsync("cust-123");
var orders = await repo.QueryCollectionAsync("cust-123", c => c.Orders);

// Update
loaded.Orders[0].Status = "Shipped";
await repo.SaveAsync(loaded);

// Delete
await repo.DeleteAsync("cust-123");

Automatic Batch Transactions

var customer = new Customer { CustomerId = "cust-123" };
customer.Orders.Add(new Order { Amount = 99.99m });
customer.Orders.Add(new Order { Amount = 45.50m });
customer.Addresses.Add(new Address { City = "Seattle" });

await repo.SaveAsync(customer);
//   โœ… All entities saved in ONE atomic batch operation
//   โœ… Either all succeed or all fail (within partition)
//   โœ… Up to 100 operations per batch
//   โœ… Automatic grouping by partition key

Built-in Resilience with Polly

// Automatic retry on transient failures
await repo.SaveAsync(customer);
//   โœ… Retries on network errors
//   โœ… Exponential backoff
//   โœ… Circuit breaker protection

You don't need to:

  • โŒ Write retry logic
  • โŒ Handle transient failures
  • โŒ Implement exponential backoff
  • โŒ Track failed operations

Handles Big Data with Automatic Rollback

PartiTables automatically handles datasets larger than Azure's 100-item batch limit with automatic rollback if any batch fails:

// Save 10,000 records across 100 batches
var salesData = GenerateSalesData("store-001", 10_000);

await repo.SaveAsync(salesData);
//   โœ… Automatically split into 100-item batches
//   โœ… If ANY batch fails, ALL previous batches are rolled back
//   โœ… Your data stays consistent - it's all-or-nothing!

What happens:

  1. Automatic batching - Splits into 100-item batches
  2. Sequential submission - Submits batches one at a time
  3. Rollback on failure - If any batch fails, previous batches are automatically deleted
  4. Exception preserved - Original error is re-thrown after cleanup

Benefits:

  • โœ… No partial data - Either all items save or none do
  • โœ… Unlimited scale - Handle 10,000+ items automatically
  • โœ… Zero configuration - Works out of the box

๐Ÿ” Powerful Querying

Partition-Scoped Queries (Fast)

// Get all data for a partition
var allCustomerData = await repo.FindAsync("customer-123");

// Get specific collection (more efficient)
var orders = await repo.QueryCollectionAsync("customer-123", c => c.Orders);

// Prefix-based queries
var orders2024 = await client.QueryByPrefixAsync("customer-123", "order-2024-");

Collection Filtering

// Load and filter in memory (for small datasets)
var customer = await repo.FindAsync("customer-123");
var pendingOrders = customer.Orders.Where(o => o.Status == "Pending").ToList();

// For larger datasets, query specific collection first
var allOrders = await repo.QueryCollectionAsync("customer-123", c => c.Orders);
var shipped = allOrders.Where(o => o.Status == "Shipped").ToList();

Query Best Practices

  • โœ… Query within a single partition for best performance
  • โœ… Use prefix-based queries for time-series data
  • โœ… Load only the collections you need
  • โœ… Avoid cross-partition queries when possible
  • โœ… Filter in memory for small result sets

๐Ÿ—๏ธ How It Works

PartiTables transforms your object graph into optimized Table Storage entities:

// Your code
var patient = new Patient { PatientId = "patient-123" };
patient.Meta.Add(new PatientMeta { FirstName = "John", LastName = "Doe" });
patient.Consents.Add(new Consent { Type = "DataSharing" });
patient.Devices.Add(new DeviceLink { DeviceId = "device-001" });

await repo.SaveAsync(patient);

What happens behind the scenes:

โœ… Batch Transaction to Table Storage
PartitionKey: clinic-001
   patient-123-meta          (Auto-generated from pattern)
   patient-123-consent-a7b8  (Auto-generated from pattern)
   patient-123-device-001    (Auto-generated from pattern)

โœ… All saved atomically in one batch
โœ… Automatic retry on failure
โœ… Optimistic concurrency handled

Single Partition (Fast Queries):

PartitionKey: customer-123
RowKeys:
   customer-123-order-001
   customer-123-order-002
   customer-123-address-001

Multi-Tenant Isolation:

PartitionKey: tenant-789
RowKeys:
   tenant-789-user-001
   tenant-789-user-002
   tenant-789-setting-001

๐ŸŽฏ Perfect For

๐Ÿ“ฑ Multi-tenant SaaS | ๐Ÿ›’ Customer orders & history | ๐Ÿฅ Healthcare records
๐Ÿ“‹ Audit logs | ๐Ÿ‘ค User profiles | ๐ŸŒ IoT device data
๐Ÿ“Š Time-series metrics | ๐Ÿ”” Notification queues | ๐Ÿ“ฆ Inventory tracking
๐ŸŽซ Event ticketing | ๐Ÿ’ฌ Chat history | ๐Ÿ” Session management


๐Ÿงช Try It Now

cd PartiSample
dotnet run

Choose from 6 interactive demos showing real-world scenarios.


๐Ÿ“š Documentation


โš™๏ธ Requirements

  • .NET 8.0+
  • Azure Storage or Azurite (local development)

๐Ÿ“œ License

MIT License โ€” ยฉ 2025 PartiTech


๐Ÿค Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.3 171 10/6/2025
1.0.2 165 10/5/2025
1.0.1 174 10/5/2025
1.0.0 163 10/5/2025

v1.0.3 - Updated RowKey generation in existing entities.