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
                    
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="WebGate.Azure.TableUtils" Version="10.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WebGate.Azure.TableUtils" Version="10.1.0" />
                    
Directory.Packages.props
<PackageReference Include="WebGate.Azure.TableUtils" />
                    
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 WebGate.Azure.TableUtils --version 10.1.0
                    
#r "nuget: WebGate.Azure.TableUtils, 10.1.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 WebGate.Azure.TableUtils@10.1.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=WebGate.Azure.TableUtils&version=10.1.0
                    
Install as a Cake Addin
#tool nuget:?package=WebGate.Azure.TableUtils&version=10.1.0
                    
Install as a Cake Tool

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 version 10.x.x
  • A future uplift to net11.0 would start at package version 11.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 → column Parent_Child).
  • null property values are skipped on serialize.
  • Value types, string, and byte[] 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


2026, WebGate Consulting AG

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

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