Primary Library
<PackageReference Include="Azure.Data.Tables" Version="12.8.3" />Dario Airoldi
November 3, 2025
Azure Table Storage is a NoSQL key/attribute store service that provides fast and cost-effective storage for structured, non-relational data.
This guide covers the available approaches and libraries for accessing Azure Table Storage using C#.
Azure.Data.TablesAzure.Data.Tables SDK<!-- For dependency injection -->
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<!-- For managed identity authentication -->
<PackageReference Include="Azure.Identity" Version="1.10.4" />The TableClient class is the primary interface for interacting with Azure Table Storage. It serves as a lightweight wrapper around the Azure Table Storage REST API, providing a strongly-typed, async-first experience for .NET developers.
using Azure.Data.Tables;
using Azure.Identity;
// Using Managed Identity (basic example - see Authentication section for details)
var credential = new DefaultAzureCredential();
var serviceClient = new TableServiceClient(
new Uri("https://yourstorageaccount.table.core.windows.net/"),
credential);
var tableClient = serviceClient.GetTableClient("YourTableName");
// Ensure table exists
await tableClient.CreateIfNotExistsAsync();The TableClient class abstracts the complexity of direct HTTP REST API calls by:
REST API Foundation: Under the hood, all operations are translated into HTTP requests to the Azure Table Storage REST endpoints:
GET requests for query operationsPOST requests for insert operationsPUT/PATCH requests for update operationsDELETE requests for delete operationsAuthentication Handling: Automatically manages authentication headers (Azure AD tokens, SAS tokens, or account keys) for each REST call
Serialization/Deserialization: Converts your .NET objects to/from JSON or AtomPub XML format used by the REST API
Error Translation: Transforms HTTP status codes and error responses into meaningful .NET exceptions
Connection Management: Handles HTTP connection pooling, timeouts, and retry logic
TableServiceClient is designed for table management operations at the account level. You use it to create, delete, or list tables within your Azure Storage account. Think of it as the tool for setting up and organizing your tables.
TableClient is focused on data operations within a specific, existing table. Once a table exists, you use TableClient to insert, query, update, or delete entities (rows) in that table. It’s the tool for day-to-day data manipulation.
| Feature | TableServiceClient | TableClient |
|---|---|---|
| Primary Purpose | Account-level client for managing multiple tables | Table-level client for CRUD operations on entities |
| Operations | Create, delete, list tables | Insert, query, update, delete entities |
| Scope | Entire storage account | Single table |
| Typical Usage | Table management operations | Data operations (CRUD) |
| Authentication | Requires account-level permissions | Requires table-level permissions |
| Creation | new TableServiceClient(uri, credential) |
serviceClient.GetTableClient("TableName") |
| Key Methods | CreateTableAsync(), DeleteTableAsync(), GetTablesAsync() |
AddEntityAsync(), QueryAsync(), UpdateEntityAsync(), DeleteEntityAsync() |
| When to Use | Setting up infrastructure, managing table lifecycle | Day-to-day data operations |
| TableServiceClient | TableClient |
|---|---|
| Account-level client for managing multiple tables | Table-level client for CRUD operations on entities |
![]() |
![]() |
Azure Table Storage supports two primary approaches for defining entities: implementing the ITableEntity interface for strongly-typed entities, or using the built-in TableEntity class for dynamic/flexible scenarios.
The ITableEntity interface is the modern, recommended approach for defining strongly-typed entities. It provides compile-time safety, IntelliSense support, and explicit control over your data model.

Key Characteristics of ITableEntity:
When to use ITableEntity:
using Azure.Data.Tables;
public class EmployeeEntity : ITableEntity
{
// Required ITableEntity properties
public string PartitionKey { get; set; } = default!; // Logical grouping (e.g., Department)
public string RowKey { get; set; } = default!; // Unique identifier within partition
public DateTimeOffset? Timestamp { get; set; } // System-managed last modified time
public ETag ETag { get; set; } // Optimistic concurrency control
// Custom business properties
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Department { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime HireDate { get; set; }
public decimal Salary { get; set; }
public bool IsActive { get; set; } = true;
// Optional: Custom business logic
public string FullName => $"{FirstName} {LastName}";
public int YearsOfService => DateTime.UtcNow.Year - HireDate.Year;
}The TableEntity class is a built-in implementation that provides dynamic property access through a dictionary-like interface. It’s perfect for scenarios where the schema is unknown, evolving, or when working with heterogeneous data.
Key Characteristics of TableEntity:
entity["PropertyName"])GetString(), GetInt32(), etc.)When to use TableEntity:
using Azure.Data.Tables;
// Create TableEntity with constructor
var employee = new TableEntity("Sales", "001")
{
["FirstName"] = "John",
["LastName"] = "Doe",
["Department"] = "Sales",
["Email"] = "john.doe@company.com",
["HireDate"] = DateTime.UtcNow,
["Salary"] = 75000.00m,
["IsActive"] = true
};
// Access properties dynamically
var firstName = employee.GetString("FirstName");
var salary = employee.GetDouble("Salary");
var hireDate = employee.GetDateTime("HireDate");
var isActive = employee.GetBoolean("IsActive");
// Check if property exists
if (employee.TryGetValue("Department", out var department))
{
Console.WriteLine($"Department: {department}");
}
// Add properties dynamically
employee["LastReview"] = DateTime.UtcNow.AddMonths(-6);
employee["PerformanceRating"] = "Excellent";| Feature | ITableEntity Implementation | TableEntity Class |
|---|---|---|
| Type Safety | ✅ Compile-time safety | ⚠️ Runtime type checking |
| IntelliSense | ✅ Full property support | ❌ Dictionary-style access |
| Performance | ✅ Optimized serialization | ⚠️ Slight overhead for type conversion |
| Schema Flexibility | ❌ Fixed at compile time | ✅ Dynamic schema changes |
| Code Maintenance | ✅ Easy refactoring | ⚠️ Property name typos possible |
| Business Logic | ✅ Custom methods/properties | ❌ External logic required |
| Learning Curve | ⚠️ Requires interface knowledge | ✅ Simple dictionary-like usage |
| Multiple Entity Types | ❌ One class per type | ✅ Single class for all types |
You can also create a hybrid approach that combines the benefits of both patterns:
public class FlexibleEmployeeEntity : ITableEntity
{
// Required ITableEntity properties
public string PartitionKey { get; set; } = default!;
public string RowKey { get; set; } = default!;
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
// Core strongly-typed properties
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
// Dynamic properties for extensibility
private readonly Dictionary<string, object> _dynamicProperties = new();
public void SetDynamicProperty(string key, object value)
{
_dynamicProperties[key] = value;
}
public T? GetDynamicProperty<T>(string key)
{
return _dynamicProperties.TryGetValue(key, out var value) && value is T typedValue
? typedValue
: default;
}
}Azure Table Storage supports OData query syntax for filtering and querying entities. Here are the supported query operations:
Supported Query Options:
$filter - Filter entities (max 15 discrete comparisons)$top - Limit number of results$select - Select specific propertiesOData Filter Operators:
eq, ne, gt, ge, lt, leand, or, notstartswith(), endswith(), contains(), length(), substring()year(), month(), day(), hour(), minute(), second()Azure Table Storage supports multiple return types for queries, giving you flexibility in how you handle data:
Uses your custom classes that implement ITableEntity - provides compile-time safety, IntelliSense support, and is best for well-defined schemas.
Built-in class for dynamic data access with dictionary-like property access (entity["PropertyName"]) and type conversion methods (GetString(), GetInt32(), etc.). Perfect for unknown schemas or schema evolution.
For advanced scenarios where you need the raw JSON - can convert TableEntity to JSON string using JsonSerializer.Serialize(entity.ToDictionary()).
Handle different entity types in the same table using discriminator properties to identify entity types and switch between different handling logic.
Store complex nested objects as JSON within table properties using custom entity classes with JSON serialization helpers.
When to Use Each Approach:
Examples of Different Return Types:
// 1. Strongly-typed entities (recommended)
var typedEmployees = tableClient.QueryAsync<EmployeeEntity>(
filter: $"PartitionKey eq 'Sales'");
await foreach (var employee in typedEmployees)
{
Console.WriteLine($"{employee.FirstName} {employee.LastName}"); // IntelliSense support
}
// 2. Dynamic TableEntity (flexible)
var dynamicEntities = tableClient.QueryAsync<TableEntity>(
filter: $"PartitionKey eq 'Sales'");
await foreach (var entity in dynamicEntities)
{
// Access properties dynamically
if (entity.TryGetValue("FirstName", out var firstName))
{
Console.WriteLine($"FirstName: {firstName}");
}
// Type conversion methods
var department = entity.GetString("Department");
var hireDate = entity.GetDateTime("HireDate");
}
// 3. Mixed entity types with discriminator
var mixedEntities = tableClient.QueryAsync<TableEntity>(
filter: $"PartitionKey eq 'Mixed'");
await foreach (var entity in mixedEntities)
{
var entityType = entity.GetString("EntityType");
switch (entityType)
{
case "Employee":
var empName = entity.GetString("FirstName");
Console.WriteLine($"Employee: {empName}");
break;
case "Customer":
var custName = entity.GetString("CompanyName");
Console.WriteLine($"Customer: {custName}");
break;
}
}// 1. Point query (most efficient - single entity by PartitionKey + RowKey)
try
{
var employee = await tableClient.GetEntityAsync<EmployeeEntity>("Sales", "001");
Console.WriteLine($"Found: {employee.Value.FirstName} {employee.Value.LastName}");
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
Console.WriteLine("Employee not found");
}
// 2. Query by PartitionKey (efficient - queries single partition)
var salesEmployees = tableClient.QueryAsync<EmployeeEntity>(
filter: $"PartitionKey eq 'Sales'",
maxPerPage: 100);
await foreach (var employee in salesEmployees)
{
Console.WriteLine($"{employee.FirstName} {employee.LastName}");
}
// 3. Complex filter queries with multiple conditions
var recentSalesEmployees = tableClient.QueryAsync<EmployeeEntity>(
filter: $"PartitionKey eq 'Sales' and HireDate gt datetime'2023-01-01T00:00:00Z'",
maxPerPage: 50);
// 4. String operations
var employeesWithJohnName = tableClient.QueryAsync<EmployeeEntity>(
filter: $"startswith(FirstName, 'John')",
maxPerPage: 100);
// 5. Range queries
var employeesByRowKeyRange = tableClient.QueryAsync<EmployeeEntity>(
filter: $"PartitionKey eq 'Sales' and RowKey ge '001' and RowKey le '100'",
maxPerPage: 100);
// 6. Select specific properties (reduces bandwidth)
var employeeNames = tableClient.QueryAsync<EmployeeEntity>(
filter: $"PartitionKey eq 'Sales'",
select: new[] { "FirstName", "LastName", "Email" },
maxPerPage: 100);
// 7. Using LINQ (alternative syntax)
var linqQuery = tableClient.Query<EmployeeEntity>(
e => e.PartitionKey == "Sales" && e.Department == "Engineering");
await foreach (var employee in linqQuery)
{
Console.WriteLine($"{employee.FirstName} works in {employee.Department}");
}
// 8. Count entities (be careful with large datasets)
var count = 0;
await foreach (var employee in tableClient.QueryAsync<EmployeeEntity>(filter: $"PartitionKey eq 'Sales'"))
{
count++;
}
Console.WriteLine($"Total employees: {count}");
// 9. Pagination handling
var pageSize = 10;
var allEmployees = new List<EmployeeEntity>();
await foreach (var page in tableClient.QueryAsync<EmployeeEntity>(
filter: $"PartitionKey eq 'Sales'",
maxPerPage: pageSize).AsPages())
{
Console.WriteLine($"Processing page with {page.Values.Count} employees");
allEmployees.AddRange(page.Values);
// Optional: break after certain number of pages
if (allEmployees.Count >= 100) break;
}Query Performance Tips:
$select to reduce bandwidthCommon Filter Examples:
// Date range
"HireDate ge datetime'2023-01-01T00:00:00Z' and HireDate le datetime'2023-12-31T23:59:59Z'"
// String contains
"contains(Email, '@company.com')"
// Numeric comparisons
"Salary gt 50000 and Salary lt 100000"
// Multiple partitions
"PartitionKey eq 'Sales' or PartitionKey eq 'Marketing'"
// Null checks
"Department ne null"
// Boolean properties
"IsActive eq true"You can insert entities using either a strongly-typed class or a dynamic approach. Here are the main options:
1. Strongly-Typed Entity (Recommended for well-defined schemas)
var employee = new EmployeeEntity
{
PartitionKey = "Sales",
RowKey = "001",
FirstName = "John",
LastName = "Doe",
Department = "Sales",
Email = "john.doe@company.com",
HireDate = DateTime.UtcNow
};
try
{
await tableClient.AddEntityAsync(employee);
Console.WriteLine("Employee added successfully");
}
catch (RequestFailedException ex) when (ex.Status == 409)
{
Console.WriteLine("Employee already exists");
}2. Dynamic Entity with TableEntity (No class required)
var dynamicEmployee = new TableEntity("Marketing", "002")
{
["FirstName"] = "Jane",
["LastName"] = "Smith",
["Department"] = "Marketing",
["Email"] = "jane.smith@company.com",
["HireDate"] = DateTime.UtcNow,
["Salary"] = 75000,
["IsActive"] = true
};
await tableClient.AddEntityAsync(dynamicEmployee);3. Insert from JSON (Dictionary-based, flexible for external data)
using System.Text.Json;
var jsonString = @"{\n \"PartitionKey\": \"Sales\",\n \"RowKey\": \"003\",\n \"FirstName\": \"Bob\",\n \"Department\": \"Sales\",\n \"Email\": \"bob@company.com\",\n \"HireDate\": \"2024-01-15T10:30:00Z\"\n}";
var jsonData = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString);
var entity = new TableEntity(jsonData);
await tableClient.AddEntityAsync(entity);4. Batch Insert from JSON Array
var jsonArray = @"[
{ \"PartitionKey\": \"Batch\", \"RowKey\": \"001\", \"FirstName\": \"Alice\" },
{ \"PartitionKey\": \"Batch\", \"RowKey\": \"002\", \"FirstName\": \"Charlie\" }
]";
var employeeList = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(jsonArray);
var batchActions = new List<TableTransactionAction>();
foreach (var empData in employeeList)
{
var batchEntity = new TableEntity(empData);
batchActions.Add(new TableTransactionAction(TableTransactionActionType.Add, batchEntity));
}
await tableClient.SubmitTransactionAsync(batchActions);When to use each approach:
// 1. Dynamic entity (no class)
var dynamicEmployee = new TableEntity("Sales", "004")
{
["FirstName"] = "Mark",
["LastName"] = "Johnson",
["Department"] = "Sales",
["Email"] = "mark.johnson@company.com",
["HireDate"] = DateTime.UtcNow
};
await tableClient.AddEntityAsync(dynamicEmployee);
// 2. Insert from JSON string
var jsonString = @"{
""PartitionKey"": ""Sales"",
""RowKey"": ""005"",
""FirstName"": ""Lucy"",
""LastName"": ""Brown"",
""Department"": ""Sales"",
""Email"": ""lucy.brown@company.com"",
""HireDate"": ""2024-02-20T09:00:00Z""
}";
var jsonData = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonString);
await tableClient.AddEntityAsync(new TableEntity(jsonData));
// 3. Batch insert from JSON array
var jsonArray = @"[
{ ""PartitionKey"": ""Sales"", ""RowKey"": ""006"", ""FirstName"": ""Tom"" },
{ ""PartitionKey"": ""Sales"", ""RowKey"": ""007"", ""FirstName"": ""Jerry"" }
]";
var employeeList = JsonSerializer.Deserialize<List<Dictionary<string, object>>>(jsonArray);
var batchActions = new List<TableTransactionAction>();
foreach (var empData in employeeList)
{
var batchEntity = new TableEntity(empData);
batchActions.Add(new TableTransactionAction(TableTransactionActionType.Add, batchEntity));
}
await tableClient.SubmitTransactionAsync(batchActions);// Get existing entity
var employee = await tableClient.GetEntityAsync<EmployeeEntity>("Sales", "001");
var entity = employee.Value;
// Modify properties
entity.Department = "Marketing";
entity.Email = "john.doe.marketing@company.com";
// Update with optimistic concurrency
try
{
await tableClient.UpdateEntityAsync(entity, entity.ETag, TableUpdateMode.Replace);
Console.WriteLine("Employee updated successfully");
}
catch (RequestFailedException ex) when (ex.Status == 412)
{
Console.WriteLine("Entity was modified by another process");
}var batchActions = new List<TableTransactionAction>();
// Add multiple entities to batch (same partition key)
for (int i = 0; i < 10; i++)
{
var employee = new EmployeeEntity
{
PartitionKey = "Sales",
RowKey = $"00{i}",
FirstName = $"Employee{i}",
LastName = "Batch",
Department = "Sales"
};
batchActions.Add(new TableTransactionAction(TableTransactionActionType.Add, employee));
}
// Execute batch
try
{
await tableClient.SubmitTransactionAsync(batchActions);
Console.WriteLine("Batch operation completed");
}
catch (RequestFailedException ex)
{
Console.WriteLine($"Batch operation failed: {ex.Message}");
}using Polly;
var retryPolicy = Policy
.Handle<RequestFailedException>(ex => ex.Status >= 500)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryCount, context) =>
{
Console.WriteLine($"Retry {retryCount} after {timespan} seconds");
});
await retryPolicy.ExecuteAsync(async () =>
{
await tableClient.AddEntityAsync(employee);
});// Program.cs or Startup.cs
using Azure.Data.Tables;
using Azure.Identity;
using Microsoft.Extensions.DependencyInjection;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<TableServiceClient>(provider =>
{
var credential = new DefaultAzureCredential();
return new TableServiceClient(
new Uri("https://yourstorageaccount.table.core.windows.net/"),
credential);
});
services.AddScoped<IEmployeeService, EmployeeService>();
}
// Service implementation
public interface IEmployeeService
{
Task<EmployeeEntity?> GetEmployeeAsync(string partitionKey, string rowKey);
Task AddEmployeeAsync(EmployeeEntity employee);
}
public class EmployeeService : IEmployeeService
{
private readonly TableClient _tableClient;
public EmployeeService(TableServiceClient serviceClient)
{
_tableClient = serviceClient.GetTableClient("Employees");
}
public async Task<EmployeeEntity?> GetEmployeeAsync(string partitionKey, string rowKey)
{
try
{
var response = await _tableClient.GetEntityAsync<EmployeeEntity>(partitionKey, rowKey);
return response.Value;
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
return null;
}
}
public async Task AddEmployeeAsync(EmployeeEntity employee)
{
await _tableClient.AddEntityAsync(employee);
}
}using Azure.Data.Tables;
using Azure.Identity;
var credential = new ClientSecretCredential(
tenantId: "your-tenant-id",
clientId: "your-client-id",
clientSecret: "your-client-secret");
var serviceClient = new TableServiceClient(
new Uri("https://yourstorageaccount.table.core.windows.net/"),
credential);try
{
await tableClient.AddEntityAsync(employee);
}
catch (RequestFailedException ex)
{
switch (ex.Status)
{
case 409: // Conflict - entity already exists
// Handle duplicate
break;
case 404: // Not Found
// Handle missing resource
break;
case 412: // Precondition Failed - ETag mismatch
// Handle concurrency conflict
break;
default:
// Handle other errors
throw;
}
}The legacy Microsoft.Azure.Cosmos.Table and WindowsAzure.Storage SDKs have been deprecated and discontinued for several important reasons:
WindowsAzure.Storage was a massive package that included all Azure Storage services (Blob, Queue, Table, File), making it heavyweight| Feature | WindowsAzure.Storage (Legacy) |
Microsoft.Azure.Cosmos.Table (Legacy) |
Azure.Data.Tables (Current) |
|---|---|---|---|
| Status | ❌ Deprecated | ❌ Deprecated | ✅ Active & Recommended |
| Target Services | Azure Table Storage only | Azure Cosmos DB Table API only | Both Azure Table Storage & Cosmos DB |
| Package Size | Large (includes all storage services) | Medium | Small (table-focused) |
| Async Support | Partial (retrofitted) | Better | Native async-first |
| Performance | Slower | Moderate | Optimized |
| Authentication | Connection strings, SAS | Connection strings, SAS | Managed Identity, SAS, Connection strings |
| .NET Core Support | Limited | Good | Full support |
| Entity Model | TableEntity inheritance |
TableEntity inheritance |
ITableEntity interface |
| Query API | Basic LINQ | Enhanced LINQ | Modern LINQ + OData |
| Batch Operations | Limited | Good | Enhanced |
| Error Handling | Basic exceptions | Enhanced | Detailed with retry policies |
| Dependency Injection | Manual setup | Manual setup | Built-in support |
If you’re migrating from the legacy Microsoft.Azure.Cosmos.Table or WindowsAzure.Storage SDKs:
<!-- REMOVE legacy packages -->
<!-- <PackageReference Include="Microsoft.Azure.Cosmos.Table" Version="1.0.8" /> -->
<!-- <PackageReference Include="WindowsAzure.Storage" Version="9.3.3" /> -->
<!-- ADD modern package -->
<PackageReference Include="Azure.Data.Tables" Version="12.8.3" />
<PackageReference Include="Azure.Identity" Version="1.10.4" />// OLD - Inheritance model
/*
public class EmployeeEntity : TableEntity
{
public EmployeeEntity() { }
public EmployeeEntity(string department, string employeeId)
{
PartitionKey = department;
RowKey = employeeId;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
*/
// NEW - Interface model
public class EmployeeEntity : ITableEntity
{
public string PartitionKey { get; set; } = default!;
public string RowKey { get; set; } = default!;
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
// Your custom properties
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
}// OLD - WindowsAzure.Storage
/*
var storageAccount = CloudStorageAccount.Parse(connectionString);
var tableClient = storageAccount.CreateCloudTableClient();
var table = tableClient.GetTableReference("Employees");
await table.CreateIfNotExistsAsync();
*/
// OLD - Microsoft.Azure.Cosmos.Table
/*
var account = CloudStorageAccount.Parse(connectionString);
var client = account.CreateCloudTableClient(new TableClientConfiguration());
var table = client.GetTableReference("Employees");
await table.CreateIfNotExistsAsync();
*/
// NEW - Azure.Data.Tables
var credential = new DefaultAzureCredential();
var serviceClient = new TableServiceClient(
new Uri("https://yourstorageaccount.table.core.windows.net/"),
credential);
var tableClient = serviceClient.GetTableClient("Employees");
await tableClient.CreateIfNotExistsAsync();// OLD - Insert operation
/*
var insertOperation = TableOperation.Insert(employee);
var result = await table.ExecuteAsync(insertOperation);
*/
// NEW - Insert operation
await tableClient.AddEntityAsync(employee);
// OLD - Query operation
/*
var query = new TableQuery<EmployeeEntity>()
.Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "Sales"));
var results = await table.ExecuteQuerySegmentedAsync(query, null);
*/
// NEW - Query operation
var employees = tableClient.QueryAsync<EmployeeEntity>(
filter: $"PartitionKey eq 'Sales'");Moving to Azure.Data.Tables provides:
✅ Better Performance - Optimized for modern async patterns
✅ Unified SDK - Works with both Azure Table Storage and Cosmos DB
✅ Enhanced Security - Built-in support for Managed Identity
✅ Improved Developer Experience - Better IntelliSense and error messages
✅ Future-Proof - Active development and feature updates
✅ Smaller Package Size - Focused on table operations only
✅ Better Error Handling - Detailed exceptions with retry policies
| SDK | Last Update | Support Status | Recommendation |
|---|---|---|---|
WindowsAzure.Storage |
March 2021 | ❌ End of Life | Migrate immediately |
Microsoft.Azure.Cosmos.Table |
October 2021 | ❌ Deprecated | Migrate immediately |
Azure.Data.Tables |
Current | ✅ Active Development | ✅ Use for all new projects |
⚠️ Important: Microsoft will not provide security updates or bug fixes for legacy SDKs. Migration to
Azure.Data.Tablesis strongly recommended for security and compatibility reasons.
Official Documentation: Azure Table Storage Documentation
Comprehensive documentation covering Azure Table Storage concepts, capabilities, limitations, and service-level features. Essential for understanding storage account setup, pricing models, scalability limits, and architectural considerations when designing table storage solutions.
SDK Reference: Azure.Data.Tables Reference
Complete API reference documentation for the Azure.Data.Tables SDK, including all classes, methods, properties, and their signatures. Critical development resource for understanding method parameters, return types, exceptions, and proper usage patterns when writing table storage code.
Samples: Azure SDK for .NET Samples
Official code samples demonstrating real-world implementation patterns, authentication methods, CRUD operations, and advanced scenarios. Provides practical examples of best practices, error handling, and common use cases that developers can adapt for their specific table storage implementations.
Migration Guide: Migrating to Azure.Data.Tables
Step-by-step guide for migrating from legacy SDKs (WindowsAzure.Storage, Microsoft.Azure.Cosmos.Table) to the modern Azure.Data.Tables SDK. Essential for teams upgrading existing applications, providing code comparisons, breaking change explanations, and migration strategies to ensure smooth transitions.
The Azure.Data.Tables SDK is the recommended approach for accessing Azure Table Storage from C#. It provides:
Choose Azure Table Storage for cost-effective NoSQL storage, or Azure Cosmos DB Table API when you need premium features like global distribution and guaranteed low latency.