TableStorage.Fluent
December 13, 2025 ยท View on GitHub
Provides fluent entity types for storing multiple entity types in a single Azure Table Storage table. This package enables polymorphic table storage by allowing you to store different entity types in the same table using a discriminator pattern.
This package supports 2 to 4 generic type parameters. For support of 5 to 16 generic type parameters, see TableStorage.Fluent.Extended.
Features
- Store multiple entity types in a single table
- Type-safe discriminated union entities
- Support for 2 to 4 different entity types per table
- Three discriminator strategies:
$type,PartitionKey, andRowKey - Implicit conversion operators for seamless type handling
- Pattern matching with
SwitchCaseandSwitchCaseOrDefaultmethods
Installation
dotnet add package TableStorage.Core
dotnet add package TableStorage
dotnet add package TableStorage.Fluent
Usage
Basic Fluent Entity
Define your entity types using the standard [TableSet] attribute:
[TableSet]
public partial class Customer
{
public string Name { get; set; }
public string Email { get; set; }
}
[TableSet]
public partial class Order
{
public string OrderNumber { get; set; }
public decimal Amount { get; set; }
}
Create a table that can store both types using FluentTableEntity<T1, T2>:
[TableContext]
public partial class MyTableContext
{
public TableSet<FluentTableEntity<Customer, Order>> MixedEntities { get; set; }
}
Working with Fluent Entities
Store entities using implicit conversion:
var customer = new Customer { Name = "John Doe", Email = "john@example.com" };
FluentTableEntity<Customer, Order> fluentEntity = customer; // Implicit conversion
await context.MixedEntities.AddEntityAsync(fluentEntity);
Retrieve and work with entities:
var entity = await context.MixedEntities.GetEntityOrDefaultAsync(partitionKey, rowKey);
// Check the backing type
var backingType = entity.GetBackingType(); // Returns FluentBackingType.First or Second
// Get the actual type
var actualType = entity.GetActualType(); // Returns typeof(Customer) or typeof(Order)
// Pattern matching with SwitchCase
var result = entity.SwitchCase(
case1: customer => $"Customer: {customer.Name}",
case2: order => $"Order: {order.OrderNumber}"
);
Discriminator Strategies
1. FluentTableEntity<T1, T2> - Uses $type discriminator
The default fluent entity adds a $type property to distinguish between entity types:
public TableSet<FluentTableEntity<Customer, Order>> Entities { get; set; }
2. FluentPartitionTableEntity<T1, T2> - Uses PartitionKey as discriminator
Uses the PartitionKey to determine the entity type. The partition key will be set to the type name:
public TableSet<FluentPartitionTableEntity<Customer, Order>> Entities { get; set; }
3. FluentRowTypeTableEntity<T1, T2> - Uses RowKey as discriminator
Uses the RowKey to determine the entity type. The row key will be set to the type name:
public TableSet<FluentRowTypeTableEntity<Customer, Order>> Entities { get; set; }
Multiple Entity Types
Fluent entities support up to 16 different types. Simply add more type parameters:
public TableSet<FluentTableEntity<Type1, Type2, Type3, Type4, Type5>> MultiTypeTable { get; set; }
Safe Type Extraction
Use SwitchCaseOrDefault for safe handling when you're not sure which type is stored:
var value = entity.SwitchCaseOrDefault(
case1: customer => ProcessCustomer(customer),
case2: order => ProcessOrder(order),
defaultCase: () => "Unknown type"
);
Directly get the underlying value:
var value = entity.GetValue(); // Throws if NotInitialized
var valueOrNull = entity.GetValueOrDefault(); // Returns null if NotInitialized
Implicit Conversions
Fluent entities support implicit conversions in both directions:
// To FluentTableEntity
Customer customer = new Customer { Name = "Jane" };
FluentTableEntity<Customer, Order> fluent = customer;
// From FluentTableEntity
Customer retrievedCustomer = fluent; // Implicit cast
Advanced Scenarios
Querying Mixed Entity Tables
You can query fluent entity tables using standard LINQ operations:
await foreach (var entity in context.MixedEntities)
{
var type = entity.GetBackingType();
switch (type)
{
case FluentBackingType.First:
Customer customer = entity;
Console.WriteLine($"Customer: {customer.Name}");
break;
case FluentBackingType.Second:
Order order = entity;
Console.WriteLine($"Order: {order.OrderNumber}");
break;
}
}
Use Cases
Fluent entities are ideal for scenarios where:
- You need to store related but different entity types in a single table for efficient partitioning
- You want to maintain a unified query interface across multiple entity types
- You need polymorphic storage without creating separate tables
- You're implementing event sourcing or activity streams with multiple event types
Related Packages
- TableStorage.Fluent.Extended - Extended support for 5 to 16 generic type parameters
- TableStorage
- TableStorage.Core