TableStorage.Abstracts 1.6.0

dotnet add package TableStorage.Abstracts --version 1.6.0
                    
NuGet\Install-Package TableStorage.Abstracts -Version 1.6.0
                    
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="TableStorage.Abstracts" Version="1.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TableStorage.Abstracts" Version="1.6.0" />
                    
Directory.Packages.props
<PackageReference Include="TableStorage.Abstracts" />
                    
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 TableStorage.Abstracts --version 1.6.0
                    
#r "nuget: TableStorage.Abstracts, 1.6.0"
                    
#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 TableStorage.Abstracts@1.6.0
                    
#: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=TableStorage.Abstracts&version=1.6.0
                    
Install as a Cake Addin
#tool nuget:?package=TableStorage.Abstracts&version=1.6.0
                    
Install as a Cake Tool

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.

Build status NuGet Version Coverage Status

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.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
1.6.0 341 8/28/2025
1.5.2 387 5/25/2025
1.5.1 530 2/21/2025
1.5.0 158 2/17/2025
1.4.2 223 9/10/2024
1.4.1 143 7/26/2024
1.4.0 186 5/10/2024
1.3.0 249 4/26/2024
1.2.0 147 4/26/2024
1.1.0 160 4/10/2024
1.0.0 158 4/9/2024