A simplified, modern .NET client library for Azure Table Storage with built-in error handling and intuitive APIs. This library provides an easy-to-use wrapper around Azure.Data.Tables SDK with comprehensive entity operations, flexible querying with OData filters, batch transactions, and type-safe generic support. Perfect for NoSQL data storage scenarios requiring schema-less data with partition and row key indexing. Supports full CRUD operations, partition-based queries, optimistic concurrency with ETags, and atomic batch transactions. Compatible with .NET Standard 2.0+, .NET 7, 8, and 9. Ideal for cloud-native applications, microservices, and distributed systems requiring fast, scalable NoSQL storage with Azure integration.
$ dotnet add package AzureStorage.Standard.TablesA simplified, modern .NET client library for Azure Table Storage with built-in error handling and intuitive APIs for managing tables and entities.
Simplified API - Easy-to-use wrapper around Azure.Data.Tables SDK Full CRUD Operations - Insert, query, update, and delete entities Table Management - Create, delete, and list tables Flexible Querying - OData filter support for complex queries Batch Transactions - Atomic operations on multiple entities Partition Key Queries - Optimized queries by partition Type-Safe Entities - Generic support for custom entity types Comprehensive Error Handling - Detailed exception information Extensive Documentation - Full XML documentation for IntelliSense
dotnet add package AzureStorage.Standard.Tables
Or via Package Manager Console:
Install-Package AzureStorage.Standard.Tables
using AzureStorage.Standard.Tables;
using AzureStorage.Standard.Core;
var options = new StorageOptions
{
ConnectionString = "DefaultEndpointsProtocol=https;AccountName=..."
};
var tableClient = new TableClient(options);
using AzureStorage.Standard.Core.Domain.Models;
public class Customer : ITableEntity
{
public string PartitionKey { get; set; } // e.g., "USA"
public string RowKey { get; set; } // e.g., Customer ID
public DateTimeOffset? Timestamp { get; set; }
public string ETag { get; set; }
// Custom properties
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
await tableClient.CreateTableIfNotExistsAsync("Customers");
var customer = new Customer
{
PartitionKey = "USA",
RowKey = "customer-001",
Name = "John Doe",
Email = "john@example.com",
Age = 30
};
await tableClient.InsertEntityAsync("Customers", customer);
// Get a specific entity
var customer = await tableClient.GetEntityAsync<Customer>(
tableName: "Customers",
partitionKey: "USA",
rowKey: "customer-001"
);
// Query by partition key (efficient!)
var usCustomers = await tableClient.QueryByPartitionKeyAsync<Customer>(
tableName: "Customers",
partitionKey: "USA"
);
// Query with OData filter
var filter = "Age gt 25 and Email ne null";
var results = await tableClient.QueryEntitiesAsync<Customer>(
tableName: "Customers",
filter: filter
);
customer.Age = 31;
await tableClient.UpdateEntityAsync("Customers", customer);
await tableClient.DeleteEntityAsync(
tableName: "Customers",
partitionKey: "USA",
rowKey: "customer-001"
);
// Insert or replace (overwrites all properties)
await tableClient.UpsertEntityAsync("Customers", customer);
var actions = new List<TableTransactionAction>
{
new TableTransactionAction
{
ActionType = TableTransactionActionType.Insert,
Entity = customer1
},
new TableTransactionAction
{
ActionType = TableTransactionActionType.Update,
Entity = customer2,
ETag = "*"
},
new TableTransactionAction
{
ActionType = TableTransactionActionType.Delete,
Entity = customer3,
ETag = "*"
}
};
// All operations must be in the same partition
await tableClient.ExecuteBatchAsync("Customers", actions);
var customer = await tableClient.GetEntityAsync<Customer>("Customers", "USA", "customer-001");
customer.Age = 32;
// Update only if ETag matches (no concurrent modifications)
await tableClient.UpdateEntityAsync(
tableName: "Customers",
entity: customer,
eTag: customer.ETag // Will fail if entity was modified by another process
);
var results = await tableClient.QueryEntitiesAsync<Customer>(
tableName: "Customers",
filter: "Age gt 21",
maxPerPage: 100 // Limit results per page
);
// Warning: Retrieves ALL entities - use filtering for large tables
var allCustomers = await tableClient.GetAllEntitiesAsync<Customer>("Customers");
// List all tables
var tables = await tableClient.ListTablesAsync();
// Check if table exists
bool exists = await tableClient.TableExistsAsync("Customers");
// Delete table
await tableClient.DeleteTableAsync("Customers");
var options = new StorageOptions
{
ConnectionString = "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=..."
};
var options = new StorageOptions
{
AccountName = "myaccount",
AccountKey = "your-account-key"
};
var options = new StorageOptions
{
ServiceUri = new Uri("https://myaccount.table.core.windows.net")
};
Choose partition keys that distribute data evenly:
// Good: Distributes by country
PartitionKey = "USA"
// Bad: All entities in one partition
PartitionKey = "AllCustomers"
Use unique identifiers:
// Good: Unique customer ID
RowKey = $"customer-{customerId}"
// Good: Timestamp for time-series data
RowKey = DateTime.UtcNow.Ticks.ToString()
Always use partition key when possible:
// Efficient: Queries single partition
var results = await tableClient.QueryByPartitionKeyAsync<Customer>("Customers", "USA");
// Inefficient: Scans all partitions
var results = await tableClient.GetAllEntitiesAsync<Customer>("Customers");
try
{
await tableClient.InsertEntityAsync("Customers", customer);
}
catch (AzureStorageException ex)
{
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine($"Error Code: {ex.ErrorCode}");
Console.WriteLine($"Status Code: {ex.StatusCode}");
}
For complete documentation, visit the GitHub repository.
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
For issues, questions, or suggestions, please open an issue on GitHub.