TableStorage.Abstracts
1.6.0
dotnet add package TableStorage.Abstracts --version 1.6.0
NuGet\Install-Package TableStorage.Abstracts -Version 1.6.0
<PackageReference Include="TableStorage.Abstracts" Version="1.6.0" />
<PackageVersion Include="TableStorage.Abstracts" Version="1.6.0" />
<PackageReference Include="TableStorage.Abstracts" />
paket add TableStorage.Abstracts --version 1.6.0
#r "nuget: TableStorage.Abstracts, 1.6.0"
#:package TableStorage.Abstracts@1.6.0
#addin nuget:?package=TableStorage.Abstracts&version=1.6.0
#tool nuget:?package=TableStorage.Abstracts&version=1.6.0
TableStorage.Abstracts
A .NET library that provides abstract base classes and repository patterns for Azure Table Storage, simplifying data access and operations with a clean, testable interface.
Installation
Install the package via NuGet Package Manager:
# Package Manager Console
Install-Package TableStorage.Abstracts
# .NET CLI
dotnet add package TableStorage.Abstracts
NuGet Package: TableStorage.Abstracts
Features
- Repository Pattern: Clean abstraction over Azure Table Storage operations
- Query Operations: Find single entities, collections, and paginated results
- CRUD Operations: Full Create, Read, Update, Delete support with async/await
- Batch Processing: Efficient bulk insert, update, and delete operations
- Dependency Injection: Built-in support for Microsoft.Extensions.DependencyInjection
- Auto-Initialization: Automatic table creation on first use
- Key Generation: Automatic RowKey generation using ULID
- Multi-Framework: Supports .NET Standard 2.0, .NET 8.0, and .NET 9.0
Quick Start
1. Define Your Entity
Create an entity class that inherits from TableEntityBase
or implements ITableEntity
:
public class User : TableEntityBase
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
}
2. Configure Dependency Injection
Register the repository services in your Program.cs
or Startup.cs
:
// Using connection string from configuration
builder.Services.AddTableStorageRepository("AzureStorage");
// Or with direct connection string
builder.Services.AddTableStorageRepository("UseDevelopmentStorage=true");
Configuration Example (appsettings.json
):
{
"AzureStorage": "UseDevelopmentStorage=true"
}
3. Use the Repository
Inject and use ITableRepository<T>
in your services:
public class UserService
{
private readonly ITableRepository<User> _userRepository;
public UserService(ITableRepository<User> userRepository)
{
_userRepository = userRepository;
}
public async Task<User> CreateUserAsync(string name, string email)
{
var user = new User { Name = name, Email = email };
return await _userRepository.CreateAsync(user);
}
public async Task<User?> GetUserAsync(string rowKey, string partitionKey)
{
return await _userRepository.FindAsync(rowKey, partitionKey);
}
}
Usage Guide
Alternative Registration Methods
Register with connection string from configuration:
services.AddTableStorageRepository("AzureStorage");
Register with direct connection string:
services.AddTableStorageRepository("UseDevelopmentStorage=true");
Resolving Dependencies
Resolve ITableRepository<T>
:
var repository = serviceProvider.GetRequiredService<ITableRepository<User>>();
Resolve TableServiceClient
:
var tableServiceClient = serviceProvider.GetRequiredService<TableServiceClient>();
Custom Repository Implementation
Create a custom repository by inheriting from TableRepository<T>
:
public class UserRepository : TableRepository<User>
{
public UserRepository(ILoggerFactory logFactory, TableServiceClient tableServiceClient)
: base(logFactory, tableServiceClient)
{ }
protected override void BeforeSave(User entity)
{
// Use email as partition key for better data distribution
entity.PartitionKey = entity.Email;
base.BeforeSave(entity);
}
// Override default table name (uses typeof(TEntity).Name by default)
protected override string GetTableName() => "UserProfiles";
// Custom business logic methods
public async Task<IReadOnlyList<User>> FindActiveUsersAsync()
{
return await FindAllAsync(u => u.IsActive);
}
public async Task<User?> FindByEmailAsync(string email)
{
return await FindOneAsync(u => u.Email == email);
}
}
Register your custom repository:
services.AddTableStorageRepository("AzureStorage");
services.AddScoped<UserRepository>();
Understanding RowKey and PartitionKey
Azure Table Storage uses a two-part key system that's fundamental to performance and scalability. Understanding these keys is crucial for designing efficient table storage solutions.
Key Components
RowKey: Unique identifier within a partition
- Must be unique within the partition
- Automatically generated using ULID if not explicitly set
- Combined with PartitionKey forms the primary key
- Case-sensitive string (max 1KB)
PartitionKey: Logical grouping for related entities
- Determines physical storage distribution
- Entities with the same PartitionKey are stored together
- Defaults to RowKey value if not explicitly set
- Case-sensitive string (max 1KB)
Key Generation Examples
Automatic key generation (recommended for simple scenarios):
var user = new User
{
Name = "John Doe",
Email = "john@example.com"
// RowKey will be auto-generated using ULID
// PartitionKey will default to the generated RowKey value
};
await repository.CreateAsync(user);
// Result: RowKey = "01ARZ3NDEKTSV4RRFFQ69G5FAV", PartitionKey = "01ARZ3NDEKTSV4RRFFQ69G5FAV"
Explicit key assignment for custom partitioning:
var user = new User
{
Name = "John Doe",
Email = "john@example.com",
PartitionKey = "Department_Engineering", // Group by department
RowKey = "user_john_doe" // Custom identifier
};
Strategic partitioning for better performance:
// Time-based partitioning (good for log data)
var eventTime = DateTimeOffset.UtcNow;
var logEntry = new LogEvent
{
Message = "User login",
PartitionKey = KeyGenerator.GeneratePartitionKey(eventTime), // 5-minute intervals with reverse chronological ordering
RowKey = KeyGenerator.GenerateRowKey(eventTime) // ULID with reverse chronological ordering
};
// Geographic partitioning
var order = new Order
{
ProductName = "Widget",
PartitionKey = "Region_US_West", // Group by region
RowKey = $"order_{Guid.NewGuid()}"
};
Performance Implications
Query Performance:
- Queries filtering by both PartitionKey and RowKey are fastest (point queries)
- Queries filtering by PartitionKey only are efficient (partition scans)
- Queries without PartitionKey scan the entire table (avoid when possible)
// Fastest: Point query (both keys)
var user = await repository.FindAsync("user_001", "Department_Engineering");
// Fast: Partition scan
var deptUsers = await repository.FindAllAsync(u => u.PartitionKey == "Department_Engineering");
// Slow: Table scan (avoid if possible)
var activeUsers = await repository.FindAllAsync(u => u.IsActive);
Query Operations
Finding Single Entities
Find an entity by row and partition key (both required by Azure Table Storage):
var user = await repository.FindAsync(rowKey, partitionKey);
if (user != null)
{
Console.WriteLine($"Found user: {user.Name}");
}
Finding Multiple Entities
Find all entities matching a filter expression:
var activeUsers = await repository.FindAllAsync(u => u.IsActive);
Find a single entity by filter (returns first match):
var user = await repository.FindOneAsync(u => u.Email == "john@example.com");
Paginated Queries
Azure Table Storage supports forward-only paging using continuation tokens:
var pageResult = await repository.FindPageAsync(
filter: u => u.IsActive,
pageSize: 20);
Console.WriteLine($"Found {pageResult.Items.Count} users");
// Loop through all pages
while (!string.IsNullOrEmpty(pageResult.ContinuationToken))
{
pageResult = await repository.FindPageAsync(
filter: u => u.IsActive,
continuationToken: pageResult.ContinuationToken,
pageSize: 20);
Console.WriteLine($"Next page: {pageResult.Items.Count} users");
}
CRUD Operations
Create Operations
Create a new entity:
var user = new User
{
Name = "John Doe",
Email = "john@example.com",
IsActive = true
};
var createdUser = await repository.CreateAsync(user);
Console.WriteLine($"Created user with ID: {createdUser.RowKey}");
Update Operations
Update an existing entity:
user.Name = "John Smith";
var updatedUser = await repository.UpdateAsync(user);
Upsert Operations
Save (create or update) an entity:
var savedUser = await repository.SaveAsync(user);
Delete Operations
Delete an entity:
await repository.DeleteAsync(user);
// Or delete by keys
await repository.DeleteAsync(rowKey, partitionKey);
Batch Operations
Perform bulk operations efficiently using either the core BatchAsync
method or the convenient extension methods:
Note: Azure Table Storage batch operations are limited to 100 entities per batch and all entities must share the same PartitionKey. The batch methods automatically handle these limitations by grouping entities by PartitionKey and chunking them into batches of 100 items.
Core Batch Method
The fundamental batch operation method with explicit transaction type:
var users = new List<User>
{
new() { Name = "User 1", Email = "user1@example.com" },
new() { Name = "User 2", Email = "user2@example.com" },
new() { Name = "User 3", Email = "user3@example.com" }
};
// Insert new entities
await repository.BatchAsync(users, TableTransactionActionType.Add);
// Update existing entities
await repository.BatchAsync(users, TableTransactionActionType.UpdateReplace);
// Merge changes (partial updates)
await repository.BatchAsync(users, TableTransactionActionType.UpdateMerge);
// Delete entities
await repository.BatchAsync(users, TableTransactionActionType.Delete);
Extension Methods for Convenience
Use the convenient extension methods for common batch operations:
Bulk Create
var users = new List<User>
{
new() { Name = "User 1", Email = "user1@example.com" },
new() { Name = "User 2", Email = "user2@example.com" },
new() { Name = "User 3", Email = "user3@example.com" }
};
// Create multiple entities (equivalent to TableTransactionActionType.Add)
var createdCount = await repository.CreateBatchAsync(users);
Console.WriteLine($"Created {createdCount} users");
Bulk Update
// Update multiple entities (equivalent to TableTransactionActionType.UpdateReplace)
var updatedCount = await repository.UpdateBatchAsync(users);
Console.WriteLine($"Updated {updatedCount} users");
Bulk Save (Upsert)
// Save multiple entities - insert if new, update if exists (equivalent to TableTransactionActionType.UpsertReplace)
var savedCount = await repository.SaveBatchAsync(users);
Console.WriteLine($"Saved {savedCount} users");
Bulk Delete
// Delete multiple entities
var deletedCount = await repository.DeleteBatchAsync(users);
Console.WriteLine($"Deleted {deletedCount} users");
Advanced Bulk Delete
Delete entities by filter expression with automatic pagination:
// Delete all inactive users (processes in pages to limit memory usage)
var deletedCount = await repository.DeleteBatchAsync(u => !u.IsActive);
Console.WriteLine($"Deleted {deletedCount} inactive users");
// Delete using OData filter query
var deletedCount = await repository.DeleteBatchAsync("IsActive eq false");
Console.WriteLine($"Deleted {deletedCount} inactive users");
Performance Tip: The filter-based delete methods automatically handle pagination to prevent memory issues when deleting large numbers of entities. Each page is processed using batch operations for optimal performance.
Advanced Usage
Custom Key Generation
Override the default ULID key generation:
public class CustomRepository : TableRepository<User>
{
public override string NewRowKey()
{
return Guid.NewGuid().ToString();
}
}
Table Initialization
Tables are automatically created on first use. To manually initialize:
var tableClient = await repository.GetClientAsync();
await tableClient.CreateIfNotExistsAsync();
Working with TableServiceClient
Access the underlying Azure Table Storage client:
var tableServiceClient = serviceProvider.GetRequiredService<TableServiceClient>();
var tables = tableServiceClient.QueryTablesAsync();
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Links
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. 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 is compatible. 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. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Azure.Data.Tables (>= 12.11.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.8)
- ulid (>= 1.4.1)
-
net8.0
- Azure.Data.Tables (>= 12.11.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.8)
- ulid (>= 1.4.1)
-
net9.0
- Azure.Data.Tables (>= 12.11.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.8)
- ulid (>= 1.4.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on TableStorage.Abstracts:
Package | Downloads |
---|---|
Serilog.Dashboard.Provider.TableStorage
Serilog dashboard log viewer |
GitHub repositories
This package is not used by any popular GitHub repositories.