A 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.TablesOr via Package Manager Console:
Install-Package AzureStorage.Standard.Tablesusing 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");- .NET Standard 2.0
- .NET Standard 2.1
- .NET 7.0
- .NET 8.0
- .NET 9.0
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}");
}- AzureStorage.Standard.Blobs - Azure Blob Storage client
- AzureStorage.Standard.Queues - Azure Queue Storage client
- AzureStorage.Standard.Files - Azure File Share client
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.