WebGate.Azure.TableUtils
10.1.0
dotnet add package WebGate.Azure.TableUtils --version 10.1.0
NuGet\Install-Package WebGate.Azure.TableUtils -Version 10.1.0
<PackageReference Include="WebGate.Azure.TableUtils" Version="10.1.0" />
<PackageVersion Include="WebGate.Azure.TableUtils" Version="10.1.0" />
<PackageReference Include="WebGate.Azure.TableUtils" />
paket add WebGate.Azure.TableUtils --version 10.1.0
#r "nuget: WebGate.Azure.TableUtils, 10.1.0"
#:package WebGate.Azure.TableUtils@10.1.0
#addin nuget:?package=WebGate.Azure.TableUtils&version=10.1.0
#tool nuget:?package=WebGate.Azure.TableUtils&version=10.1.0
WebGate.Azure.TableUtils
Extensions for Azure.Data.Tables with typed CRUD clients. Supports complex nested entities, arrays, and IEnumerable via flattened table properties.
The main focus is usage in Azure Functions. Table access uses a Storage Account connection string. SAS and other authentication methods are not supported yet, but can be added when required.
Package: WebGate.Azure.TableUtils
License: Apache-2.0
dotnet add package WebGate.Azure.TableUtils
Target Framework & Versioning
| Target Framework | net10.0 |
| Package version | 10.x.x |
The NuGet package major version matches the .NET target framework major version.
net10.0→ package version10.x.x- A future uplift to
net11.0would start at package version11.0.0
Within a major line, use minor/patch for library changes that stay on the same TFM.
Entity mapping
POCOs are mapped to Azure Table properties by reflection:
- Readable/writable properties are included.
- Nested objects are flattened with
_as separator (Parent.Child→ columnParent_Child). nullproperty values are skipped on serialize.- Value types,
string, andbyte[]are stored directly. - Dedicated converters handle enums, TimeSpan, decimal (InvariantCulture string), arrays, and IEnumerable (JSON via Newtonsoft.Json).
ObjectSerializer (POCO → properties) and ObjectBuilder (TableEntity → POCO) implement this mapping. Clients use them automatically.
TableFilter
Builds OData filter strings for QueryAsync. Property names are identifiers; values are Azure Tables literals (quotes escaped, datetime'…', guid'…', true/false, 42L).
var filter = TableFilter.And(
TableFilter.PartitionKeyEquals("MyPoco"),
TableFilter.Equal(nameof(MyPoco.Id), "001"),
TableFilter.LessThanOrEqual(nameof(MyPoco.DTOValue), DateTimeOffset.UtcNow));
| Method | Use |
|---|---|
Equal / NotEqual / GreaterThan / GreaterThanOrEqual / LessThan / LessThanOrEqual |
One method each; value type is formatted at runtime |
Compare / FromValue |
(property, comparison, object) — QueryComparisons.* |
And / Or / Combine |
params filters; empty/null parts are skipped. Combine(TableOperators.*, ...) |
PartitionKeyEquals / RowKeyEquals |
Key filters with escaping |
Dates take DateTimeOffset only. Convert DateTime with TableDateTime.EnsureUtc before building a DateTimeOffset (Unspecified is treated as already-UTC ticks). Use EnsureUtc on write paths as well — Azure.Data.Tables rejects DateTimeKind.Unspecified.
Nested TableUtils columns use flattened names (Parent_Child_Id), not CLR navigation.
ExtendedAzureTableClientService
Registers and resolves TypedAzureTableClient<T> and MultiEntityAzureTableClient instances.
Create a service
var connectionString = "MY_STRING";
var extendedTableService = new ExtendedAzureTableClientService(connectionString);
Initialize the service in Startup / Program.cs of an Azure Function (or host).
Register TypedAzureTableClients
One table per POCO type:
var simplePocoAzureTableClient = extendedTableService.CreateAndRegisterTableClient<SimplePoco>("simplePocoTable");
var parentPocoAzureTableClient = extendedTableService.CreateAndRegisterTableClient<ParentPoco>("parentPocoTable");
Register an already initialized TableClient
extendedTableService.AddInitializedTableClient<SimplePoco>(existingTableClient);
Resolve a TypedAzureTableClient
var simplePocoAzureTableClient = extendedTableService.GetTypedTableClient<SimplePoco>();
Throws ArgumentOutOfRangeException if the type was not registered.
Register a MultiEntityAzureTableClient
Store different entity types in one table. Row keys are prefixed with the registered type name (or a custom prefix):
var multiEntityTableClient = extendedTableService.CreateAndRegisterMultiEntityTableClient("allpocos");
multiEntityTableClient.RegisterType<SimplePoco>();
multiEntityTableClient.RegisterType<MainWithParent>("mwp");
multiEntityTableClient.RegisterType<PocoWithListChildren>();
SimplePoco and PocoWithListChildren use their type name as prefix; MainWithParent uses mwp.
Resolve a MultiEntityAzureTableClient
var multiEntityTableClient = extendedTableService.GetMultiEntityAzureTableClientByTableName("allpocos");
The table name used at registration is the lookup key.
TableEntityResult<T>
Results from both clients are wrapped in TableEntityResult<T>. For TypedAzureTableClient<T>, T is the POCO type. For MultiEntityAzureTableClient list queries, T is object.
public class TableEntityResult<T>(ITableEntity tableEntity, T entity)
{
public string RowKey { get; set; } = tableEntity.RowKey;
public string PartitionKey { set; get; } = tableEntity.PartitionKey;
public ETag ETag { get; set; } = tableEntity.ETag;
public DateTimeOffset? Timestamp { get; set; } = tableEntity.Timestamp;
public T Entity { get; set; } = entity;
}
TypedAzureTableClient<T>
Decorator around Azure.Data.Tables.TableClient with POCO serialize/deserialize (including nested entities, arrays, and IEnumerable).
Get from service
var typedTableClient = extendedTableService.GetTypedTableClient<MyPoco>();
Initialize inline
var connectionString = "MY_STRING";
var tableClient = new TableClient(connectionString, "MyPoco"); // Azure.Data.Tables
await tableClient.CreateIfNotExistsAsync();
var typedTableClient = new TypedAzureTableClient<MyPoco>(tableClient);
Underlying SDK client: typedTableClient.TableClient (GetTableClient() is obsolete).
Preferred 10.x surface: upserts + gets + QueryAsync (serialize/deserialize). Build filters with TableFilter. Use TableClient for deletes and other raw SDK calls.
Examples below use a client bound to MyPoco.
GetAllAsync()
List<TableEntityResult<MyPoco>> pocos = await typedTableClient.GetAllAsync();
All rows; no partition filter.
GetAllAsync(string partitionKey)
List<TableEntityResult<MyPoco>> pocos = await typedTableClient.GetAllAsync("mypoco");
All rows for the given partition key.
GetByIdAsync(string id)
TableEntityResult<MyPoco>? poco = await typedTableClient.GetByIdAsync("1018301");
Looks up by row key id. Partition key is typeof(T).ToString() (typically the full type name, e.g. MyNamespace.MyPoco). Returns null if not found.
GetByIdAsync(string rowKey, string partitionKey)
TableEntityResult<MyPoco>? poco = await typedTableClient.GetByIdAsync("9201u819", "mypoco");
Returns null if not found.
QueryAsync(string? filter)
var filter = TableFilter.And(
TableFilter.Equal(nameof(MyPoco.Id), "001"),
TableFilter.LessThanOrEqual(nameof(MyPoco.DTOValue), DateTimeOffset.UtcNow));
List<TableEntityResult<MyPoco>> pocos = await typedTableClient.QueryAsync(filter);
OData filter as supported by TableClient.QueryAsync. Build it with TableFilter (escaping, typed literals, and / or). Pass null or empty for an unfiltered query (same as GetAllAsync()).
GetAllByQueryAsync is obsolete and forwards to QueryAsync.
InsertOrMergeAsync(string rowKey, string partitionKey, object obj)
MyPoco poco = new MyPoco();
// populate poco
Azure.Response result = await typedTableClient.InsertOrMergeAsync("001", "SimplePoco", poco);
Upsert with TableUpdateMode.Merge. The parameter is object so partial DTOs (not necessarily T) can be merged.
InsertOrReplaceAsync(string rowKey, string partitionKey, object obj)
MyPoco poco = new MyPoco();
// populate poco
Azure.Response result = await typedTableClient.InsertOrReplaceAsync("001", "SimplePoco", poco);
Upsert with TableUpdateMode.Replace.
DeleteEntityAsync — obsolete (compile error)
Parameter order is reversed vs the Azure SDK:
| 1st arg | 2nd arg | |
|---|---|---|
| This library (obsolete) | rowKey |
partitionKey |
TableClient.DeleteEntityAsync |
partitionKey |
rowKey |
// old:
await typedTableClient.DeleteEntityAsync("001", "SimplePoco");
// new:
await typedTableClient.TableClient.DeleteEntityAsync("SimplePoco", "001");
MultiEntityAzureTableClient
Decorator around TableClient with the same mapping capabilities, plus multiple entity types in one table. Each registered type gets a row-key prefix ({prefix}_{rowKey}). Types must be registered before insert/get-by-type. Unregistered row prefixes on read throw ArgumentOutOfRangeException.
Get from service
var multiEntityTableClient = extendedTableService.GetMultiEntityAzureTableClientByTableName("allpocos");
Initialize inline
var connectionString = "MY_STRING";
var tableClient = new TableClient(connectionString, "allpocos"); // Azure.Data.Tables
await tableClient.CreateIfNotExistsAsync();
var multiEntityTableClient = new MultiEntityAzureTableClient(tableClient);
multiEntityTableClient.RegisterType<SimplePoco>();
multiEntityTableClient.RegisterType<MainWithParent>("mwp");
multiEntityTableClient.RegisterType<PocoWithListChildren>();
Underlying SDK client: multiEntityTableClient.TableClient (GetTableClient() is obsolete).
Preferred 10.x surface: registry + upserts + gets + QueryAsync (+ DeleteEntityByTypeAsync for prefix-aware delete). Raw delete by full row key: TableClient.DeleteEntityAsync.
Examples below assume SimplePoco, MainWithParent, and PocoWithListChildren are registered.
GetAllAsync()
List<TableEntityResult<object>> allPocos = await multiEntityTableClient.GetAllAsync();
List<SimplePoco> simplePocos = allPocos.Select(res => res.Entity).OfType<SimplePoco>().ToList();
GetAllAsync(string partitionKey)
List<TableEntityResult<object>> allPocos = await multiEntityTableClient.GetAllAsync("mypoco");
List<SimplePoco> simplePocos = allPocos.Select(res => res.Entity).OfType<SimplePoco>().ToList();
GetByIdAsync<T>(string rowKey, string partitionKey)
TableEntityResult<MyPoco>? poco = await multiEntityTableClient.GetByIdAsync<MyPoco>("9201u819", "mypoco");
Resolves the stored row key as {registeredPrefix}_{rowKey}. Returns null if not found. Throws if T is not registered.
QueryAsync(string? filter)
var filter = TableFilter.PartitionKeyEquals(partitionKey);
List<TableEntityResult<object>> allPocos = await multiEntityTableClient.QueryAsync(filter);
List<SimplePoco> simplePocos = allPocos.Select(res => res.Entity).OfType<SimplePoco>().ToList();
OData filter as supported by TableClient.QueryAsync. Pass null or empty for an unfiltered query. Needed here so row keys are still resolved via the type registry. GetAllByQueryAsync is obsolete and forwards to QueryAsync.
InsertOrMergeAsync<T>(string rowKey, string partitionKey, T obj)
MyPoco poco = new MyPoco();
// populate poco
Azure.Response result = await multiEntityTableClient.InsertOrMergeAsync("001", "SimplePoco", poco);
Stores row key as {prefix}_001. Type of obj must be registered.
InsertOrReplaceAsync<T>(string rowKey, string partitionKey, T obj)
MyPoco poco = new MyPoco();
// populate poco
Azure.Response result = await multiEntityTableClient.InsertOrReplaceAsync("001", "SimplePoco", poco);
DeleteEntityByTypeAsync<T>(string rowKey, string partitionKey)
Azure.Response result = await multiEntityTableClient.DeleteEntityByTypeAsync<SimplePoco>("001", "SimplePoco");
Builds the row key from the registered prefix for T. Prefer this when you know the entity type.
DeleteEntityAsync(completeRowKey, partitionKey) — obsolete (compile error)
Parameter order is reversed vs the Azure SDK:
| 1st arg | 2nd arg | |
|---|---|---|
| This library (obsolete) | completeRowKey |
partitionKey |
TableClient.DeleteEntityAsync |
partitionKey |
rowKey |
// old:
await multiEntityTableClient.DeleteEntityAsync(result.RowKey, result.PartitionKey);
// new:
await multiEntityTableClient.TableClient.DeleteEntityAsync(result.PartitionKey, result.RowKey);
License
Apache-2.0
Copyright
2026, WebGate Consulting AG
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Azure.Data.Tables (>= 12.11.0)
- Newtonsoft.Json (>= 13.0.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.1.0 | 91 | 8/31/2026 |
| 10.0.0 | 110 | 8/3/2026 |
| 0.1.1 | 1,695 | 5/16/2024 |
| 0.1.0 | 203 | 5/16/2024 |
| 0.1.0-alpha-1 | 218 | 4/29/2024 |