TableStorage.Abstractions.POCO 4.0.0

dotnet add package TableStorage.Abstractions.POCO --version 4.0.0
NuGet\Install-Package TableStorage.Abstractions.POCO -Version 4.0.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="TableStorage.Abstractions.POCO" Version="4.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add TableStorage.Abstractions.POCO --version 4.0.0
#r "nuget: TableStorage.Abstractions.POCO, 4.0.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.
// Install TableStorage.Abstractions.POCO as a Cake Addin
#addin nuget:?package=TableStorage.Abstractions.POCO&version=4.0.0

// Install TableStorage.Abstractions.POCO as a Cake Tool
#tool nuget:?package=TableStorage.Abstractions.POCO&version=4.0.0


Codacy Badge Build status NuGet Nuget Downloads

This project builds on top of TableStorage.Abstractions (a repository wrapper over Azure Table Storage) and TableStorage.Abstractions.TableEntityConverters such that objects to be serialized to and from Azure Table Storage are Plain Old CLR Objects (POCO) rather than TableEntities.

For secondary index support, check out TableStorage.Abstractions.POCO.SecondaryIndexes


Assume we have the following two classes, which we wish to serialize to and from Azure Table Storage:

public class Employee
  public int CompanyId { get; set; }
  public int Id { get; set; }
  public string Name { get; set; }
  public Department Department { get; set; }
public class Department
  public int Id { get; set; }
  public string Name { get; set; }

In our examples we will be using CompanyId as the partition key and (Employee) Id as the row key.


var tableStore = new PocoTableStore<Employee, int, int>("TestEmployee", "UseDevelopmentStorage=true", 
     e => e.CompanyId, e => e.Id);

Here we create our table store and specify our partition key (CompanyId) and row key (Id).


var employee = new Employee
				Name = "Test",
				CompanyId = 99,
				Id = 99,
				Department = new Department {Id = 5, Name = "Test"}

Insert Or Replace (Update)

var employee = new Employee
	Name = "Test",
	CompanyId = 99,
	Id = 99,
	Department = new Department { Id = 5, Name = "Test" }


employee.Name = "Test2";

Get Record By Partition Key And Row Key

var employee = tableStore.GetRecord(1, 42);

Get All Records In Partition

var employees = tableStore.GetByPartitionKey(1);

Delete Record


Excluding Properties From Serialization

You may have some properties that you don't want to persist to Azure Table Storage. To ignore properties, use the ignoredProperties parameter.

var tableStore = new PocoTableStore<Employee, int, int>("TestEmployee", "UseDevelopmentStorage=true",
          e => e.CompanyId, e => e.Id, e=>e.Department);

In this example we ignored the Department property.

Calculated Keys

There may be situations where you want the partition key or row key to be calculated from information outside of your object (such as date, which can be a useful partition key), from multiple properties, or a fixed key (e.g. you don't need a row key).

Here's an example of using the CompanyId and DepartmentId as partition keys.

var partitionKeyMapper = new CalculatedKeyMapper<Employee, PartitionKey>(e => $"{e.CompanyId}.{e.Department.Id}", key =>
	var parts = key.Split('.');
	var companyId = int.Parse(parts[0]);
	var departmentId = int.Parse(parts[1]);
	return new PartitionKey(companyId, departmentId);
}, key=>$"{key.CompanyId}.{key.DepartmentId}");

var rowKeyMapper = new KeyMapper<Employee, int>(e => e.Id.ToString(), int.Parse, e => e.Id,
	id => id.ToString());

var keysConverter = new CalculatedKeysConverter<Employee, PartitionKey, int>(partitionKeyMapper, rowKeyMapper);

var tableStore = new PocoTableStore<Employee, PartitionKey, int>("TestEmployee", "UseDevelopmentStorage=true", keysConverter);

If you used a previous version of this library, you may remember a more complicated, more limited constructor for PocoTableStore. We've simplified things and added some flexibility by introducing IKeysConverter, where implementations encapsulate the rules for converting to/from table storage keys.

Notice that we introduced a new class called PartitionKey. This class is a simple DTO to capture CompanyId and DepartmentId. A nice side effect of having a class for this is that we gain type safety and intellisense.

public class PartitionKey
	public PartitionKey(int companyId, int departmentId)
		CompanyId = companyId;
		DepartmentId = departmentId;
	public int CompanyId { get; }
	public int DepartmentId { get; }

Inserting data is the same as always:

var employee = new Employee
	CompanyId = 1,
	Id = 1,
	Name = "Mr. Jim CEO",
	Department = new Department { Id = 22, Name = "Executive" }

In table storage, the partition key for the above example would be "1.22" and its row key would be "1".

To retrieve the record, we can use PartitionKey to build the multi-part key.

var record = tableStore.GetRecord(new PartitionKey(1, 22), 1);
Fixed Keys

Fixed keys are really just a specialization of calculated keys. A scenario that you may run into sometimes is where you only need a single key, which is the case when you only query the data using point queries ("get by id"). In this scenario, you'll probably choose to supply a partition key and not a row key since in this case you'd get better throughput using partition keys in a high volume system (again, we are assuming a point-query-only scenario).

Note that in v1.3 of the library we've simplified fixed key scenarios by introducing a new FixedKey mapper, which will be consumed by the CalculatedKeysConverter.

Again, we will use a contrived example. Here we have use Id as partition key , and we always use the word "user" for rowkey, since this will not be used.

var partitionKeyMapper = new KeyMapper<Employee, int>(e =>e.CompanyId.ToString(), int.Parse, e => e.CompanyId, id =>id.ToString());
var rowKeyMapper = new FixedKeyMapper<Employee, int>("user");

var keysConverter = new CalculatedKeysConverter<Employee, int, int>(partitionKeyMapper, rowKeyMapper);

Inserting the data remains the same:

var employee = new Employee
	Id = 1,
	Name = "Mr. Jim CEO",
	Department = new Department { Id = 22, Name = "Executive" }


As always, we have 2 ways of querying the data:

var record = tableStore.GetRecord("1", "user");

We can also get the record using the typed overload, though in this case the second parameter is thrown away since there is no row key. I prefer to use int.Min to show that this value is thrown away.

record = tableStore.GetRecord(1, int.MinValue);

Note that our table store was PocoTableStore<Employee, int, int>, but that last generic could have been anything since it is thrown away. So if you prefer, you can make it PocoTableStore<Employee, int, string> and then query like so: var record = tableStore.GetRecord(142, "user"); which is both clear and provides type safety.

Sequential Keys

SequentialKeyMapper was introduced in v2.6 and is a bit different from other key mappers because the output isn't meant for point lookups. This key mapper assigns keys in sequential order (forward or backward). Because Azure Table Storage orders rows by row key, a sequential key allows you to use Azure Table Storage as a log.

Coupled with TableStorage.Abstractions.POCO.SecondaryIndexes , you can do things like saving a historical record when mutating your main table entity.


var pKeyMapper = new KeyMapper<Employee, int>(e => e.Id.ToString(), int.Parse, e => e.Id, id => id.ToString());

var rKeyMapper = new SequentialKeyMapper<Employee, int>(true);

var keysConverter = new CalculatedKeysConverter<Employee, int, int>(pKeyMapper, rKeyMapper);

tableStore = new PocoTableStore<Employee, int, int>("TestEmployee", "UseDevelopmentStorage=true", keysConverter);

var employee = new Employee
				CompanyId = 1,
				Id = 242443,
				Name = "1",
				Department = new Department { Id = 22, Name = "Executive" }


employee.Name = "2";

employee.Name = "3";

// order will be 3, 2, 1 because we are sorting in sequential order						

Further Filtering (Beyond Partition & Row Keys)

New to v1.2, we now include the ability to filter on properties outside of partition and row keys. Please note that this filtering occurs outside of table storage, so please consider using at least the partition key for best results.


var records = tableStore.GetByPartitionKey(1, e=>e.Name == "Jim CEO");

In this example we get all records in parition "1" where the name is "Jim CEO".


Azure Table Storage entities always have a timestamp. If your POCO has a field named Timestamp, that is a DateTimeOffset, DateTime or String, then this property will automatically be hydrated wit the timestamp provided by Azure Table Storage.

Modifications to the Timestamp property do not get persisited. This is exactly how it works with the Azure Table Storage SDK.

Considerations for taking a similar approach to ETag are being considered.

Custom Json Serialization

New to v3, you can now customize how complex fields get serialized to json.

Example (Assume you have a custom json serializer named KeysJsonConverter):

var jsonSerializerSettings = new JsonSerializerSettings
		Converters = new List<JsonConverter>{new KeysJsonConverter(typeof(Department))}

var tableStore = new PocoTableStore<Employee, int, int>("TestEmployee", "UseDevelopmentStorage=true",
	e => e.CompanyId,
	e => e.Id,
	new PocoTableStoreOptions(jsonSerializerSettings));
Custom Property Conversion For Non-Key Fields

Starting in v3.1 you can specify custom property converters for properties that are not used as Partition or Row Key fields.

This is a niche use case, but useful if you need it, for example, if dates are stored as strings in Azure Table Storage.

Here is the test object we'll be using in the example:

var employee = new EmployeeWithHireDate
    Name = "Test",
    CompanyId = 99,
    Id = 99,
    HireDate = new DateTime(1999,12,31),
    Department = new Department {Id = 5, Name = "Test"}

First we need to specify property converters. PropertyConverters is a dictionary. The key is the property name and the value is a PropertyConverter, which specifies how to convert to and from EntityProperty.

var propertyConverters = new PropertyConverters<EmployeeWithHireDate>
    [nameof(EmployeeWithHireDate.HireDate)] = new PropertyConverter<EmployeeWithHireDate>(
        e=>new EntityProperty(e.HireDate.ToString("yyyy-M-d")),
        (e,p)=>e.HireDate = DateTime.Parse(p.StringValue)

Next we need to define a key converter and pass the propertyConverters in.

var simpleKeyConverter = new SimpleKeysConverter<EmployeeWithHireDate, int, int>(e => e.CompanyId, e => e.Id,
				new JsonSerializerSettings(), propertyConverters, default);
var tableStore = new PocoTableStore<EmployeeWithHireDate, int, int>("TestEmployee", "UseDevelopmentStorage=true",
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on TableStorage.Abstractions.POCO:

Package Downloads

Geared more toward Azure Table Storage (vs CosmosDB, which has an ATS api), using an intra/inter partition (or table) secondary index pattern. This library handles keeping the indexes up to date as data gets mutated.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
4.0.0 1,153 8/30/2023
3.3.0 7,458 4/2/2022
3.2.0 8,054 1/23/2022
3.2.0-beta 1,382 1/20/2022
3.1.0 1,932 11/17/2021
3.1.0-beta 1,384 11/16/2021
3.0.1 1,749 11/3/2021
3.0.0 1,865 10/5/2021
2.7.1 5,593 8/20/2020
2.7.0 2,429 5/11/2020
2.6.1 2,414 3/27/2020
2.6.0 2,102 3/26/2020
2.4.0 2,071 3/3/2020
2.3.1 2,177 2/25/2020
2.3.0 2,122 2/25/2020
2.2.0 2,213 2/17/2020
2.1.0 2,132 4/29/2019
2.0.1 6,169 1/10/2019
2.0.0 2,147 12/26/2018
1.3.2 2,433 2/11/2018
1.3.1 2,330 1/27/2018
1.3.0 2,613 9/5/2017
1.2.0 2,423 9/3/2017
1.1.3 2,349 9/2/2017 2,452 8/31/2017
1.1.2 2,356 8/31/2017
1.1.1 2,479 8/31/2017
1.0.1 2,581 8/31/2017
1.0.0 2,595 8/30/2017

Add support for cancellation tokens