Announcing the new Azure Tables Libraries

Sean

We’re excited to announce that the Azure Tables libraries have been released for .NET, Java, JavaScript/TypeScript, and Python. The Azure Table service stores NoSQL data in the cloud with a key/attribute store schema-less design. The Table storage service can be used to store flexible data sets like user data for web applications, address books, device information, or other types of metadata.

The new libraries follow our Azure SDK Guidelines, making for an idiomatic, consistent, approachable, diagnosable, and dependable library. The new libraries use the language-specific Azure Core packages for handling requests, errors, and credentials.

Note: The Azure Tables libraries are capable of targeting both Azure Storage Table and Azure Cosmos DB Table API endpoints.

SDK Availability

The Azure Tables libraries can be downloaded from each languages preferred package manager.

Language Package Command Project Home Getting Started
Python PyPi pip install azure-data-tables link link
.NET NuGet dotnet add package Azure.Data.Tables link link
JavaScript/TypeScript npm npm install @azure/data-tables link link
Java Java Add to POM.xml file link link

This blog post assumes you have a working developer environment for your preferred programming language and you already have a Storage or Cosmos Table account. If you do not have those refer to the Getting Started entry in the above table for your preferred programming language. To follow along with these snippets you’ll need the programming language of your choice (Python, .NET, Java, JS) installed, a text editor, and a Storage or Cosmos Table account.

There will be migration guides added to each project’s homepage that will show specific examples for updating your code base to the new Azure Tables library discussed in this blog. Links to complete code examples for each language are provided at the end of the article.

Creating the clients

There are two clients for interacting with the service. The TableServiceClient can be used for account-level interactions (creating tables, setting and getting access policies) and the TableClient is used for table-level interactions (create or delete an entity, query or list entities). You can create clients with a key, Shared Access Signature, or using a connection string, all of which can be found in the Azure Portal.

Python

table_client = TableClient.from_connection_string("<your-connection-string>", "officeSupplies")
table_service_client = TableServiceClient.from_connection_String("<your-connection-string>")

.NET

TableClient client = new TableClient("connectionString", "officeSupplies");
TableServiceClient serviceClient = new TableServiceClient("connectionString");

Java

TableClient tableClient = new TableClientBuilder()
    .connectionString("<your-connection-string>")
    .tableName("officeSupplies")
    .buildClient();

JavaScript

const client = TableClient.fromConnectionString("<your-connection-string>", "officeSupplies");

Create a table and add an entity to it

Table names can be any alphanumeric string between 3 and 63 characters. A table can be created from either the TableClient or the TableServiceClient, the following snippets will show how to create one from a TableClient.

An Entity can contain up to 255 properties, with the three system properties of PartitionKey, RowKey, and Timestamp. The first two properties must be provided on all entities and the Timestamp property will be modified by the service. For more information, see the service documentation. Entities can only be inserted and queried from the TableClient.

Python

table_client = TableClient.from_connection_string("<your-connection-string>", "myTable")
table_client.create()

my_entity = {
    "PartitionKey": "markers",
    "RowKey": "id-001",
    "Product": "Markers",
    "Price": 5.00,
    "Count": 10,
    "Available": True
}

table_client.create_entity(my_entity)

.NET

var tableClient = new TableClient(
    new Uri(serviceUri),
    tableName,
    new TableSharedKeyCredential(accountName, storageAccountKey));
tableClient.Create();

var partitionKey = "markers";
var rowKey = "id-001";
var entity = new TableEntity(partitionKey, rowKey)
{
    { "Product", "Marker Set" },
    { "Price", 5.00 },
    { "Quantity", 21 }
};
tableClient.AddEntity(entity);

Java

TableClient tableClient = new TableClientBuilder()
    .connectionString("<your-connection-string>")
    .tableName(tableName)
    .buildClient();

String partitionKey = "markers";
String rowKey = "id-001";
TableEntity entity = new TableEntity(partitionKey, rowKey)
    .addProperty("Product", "Marker Set")
    .addProperty("Price", 5.00)
    .addProperty("Quantity", 21);

tableClient.createEntity(entity);

JavaScript

const tableName = "SampleCreateAndDeleteTable2";
const client = TableClient.fromConnectionString("<your-connection-string>", tableName);
await client.createTable();

const entity = {
  partitionKey: "p1",
  rowKey: "r1",
  date: new Date()
};
await client.createEntity(entity);

Querying Table Entities

The TableClient allows the user to create custom queries using OData filters. For more information on writing table and entity queries check out the service documentation.

Python

query_filter = "PartitionKey eq @pk_filter or RowKey eq @rk_filter"
parameters = {
    "pk_filter": "markers",
    "rk_filter": "id-001",
}

for entity in table_client.query_entities(query_filter, parameters=parameters):
    print(entity["Product"], entity["Count"])

.NET

string MyPK = "markers";
string MyRK = "id-001";
string filter = TableOdataFilter.Create($"PartitionKey eq {MyPK} or RowKey eq {MyRK}")

Pageable<TableEntity> entities = tableClient.Query<TableEntity>(filter: filter);

foreach (TableEntity entity in entities)
{
    Console.WriteLine($"{entity.GetString("Product")}: {entity.GetInteger("Count")}");
}

Java

ListEntitiesOptions options = new ListEntitiesOptions()
    .setFilter("PartitionKey eq 'markers' or RowKey eq 'id-001'");

for (TableEntity entity : tableClient.listEntities(options)) {
    Map<String, Object> properties = entity.getProperties();
    System.out.println(String.format("%s: %d", properties.get("Product"), properties.get("Count")));
}

JavaScript

const partitionKey = "markers";
const id = "id-001"
let entities = client.listEntities({    queryOptions: { filter: odata`PartitionKey eq ${partitionKey} or RowKey eq ${id}` });

for await (const entity of entities) {
    console.log(`${entity.Product}: ${entity.Count}`);
}

Batch Transactions

The Table service allows multiple entity operations (create, delete, update, and upsert) to be made in a single request using transactional batch operations. The transaction is an “all-or-nothing” approach, if there is a failure in one of the operations, the entire operation will fail. A transaction can perform any combination of create, delete, update, and upsert operations. For more information, see the service documentation.

Note: All operations within a transaction must target the same partition key.

Python

table_client = TableClient.from_connection_string("<your-connection-string>", "myProductTable")

entity1 = {"PartitionKey": "pk0001", "RowKey": "A1", "Name": "Marker Set", "Price": 5.0, "Quantity": 21}
entity2 = {"PartitionKey": "pk0001", "RowKey": "A2"}
entity3 = {"PartitionKey": "pk0001", "RowKey": "A3", "Name": "Pen Set", "Price": 2.0, "Quantity": 6}
entity4 = {"PartitionKey": "pk0001", "RowKey": "A4", "Name": "Pencil", "Price": 1.5, "Quantity": 100}

operations = [
    ("upsert", entity1),
    ("delete", entity2),
    ("create", entity3),
    ("update", entity4, {"mode": "replace"})
]

table_client.submit_transaction(operations)

.NET

var tableClient = new TableClient(
    new Uri(serviceUri),
    tableName,
    new TableSharedKeyCredential(accountName, storageAccountKey));

string partitionKey = "BatchInsertSample";
List<TableEntity> entityList = new List<TableEntity>
{
    new TableEntity(partitionKey, "01")
    {
        { "Product", "Marker" },
        { "Price", 5.00 },
        { "Brand", "Premium" }
    },
    new TableEntity(partitionKey, "02")
    {
        { "Product", "Pen" },
        { "Price", 3.00 },
        { "Brand", "Premium" }
    },
    new TableEntity(partitionKey, "03")
    {
        { "Product", "Paper" },
        { "Price", 0.10 },
        { "Brand", "Premium" }
    },
    new TableEntity(partitionKey, "04")
    {
        { "Product", "Glue" },
        { "Price", 1.00 },
        { "Brand", "Generic" }
    },
};

// Create the batch.
List<TableTransactionAction> addEntitiesBatch = new List<TableTransactionAction>();

// Add the entities to be added to the batch.
addEntitiesBatch.AddRange(entityList.Select(e => new TableTransactionAction(TableTransactionActionType.Add, e)));

// Submit the batch.
Response<IReadOnlyList<Response>> response = await client.SubmitTransactionAsync(addEntitiesBatch).ConfigureAwait(false);

for (int i = 0; i < entityList.Count; i++)
{
    Console.WriteLine($"The ETag for the entity with RowKey: '{entityList[i].RowKey}' is {response.Value[i].Headers.ETag}");
}

Java

TableClient tableClient = new TableClientBuilder()
    .tableName("myProductTable")
    .connectionString("<your-connection-string>")
    .buildClient();

// Now let's create a list to add transactional batch actions to.
List<TableTransactionAction> transactionActions = new ArrayList<>();

String partitionKey = "pk001";

TableEntity firstEntity = new TableEntity(partitionKey, "rk001")
    .addProperty("Name", "Marker Set")
    .addProperty("Price", 5.0)
    .addProperty("Quantity", 21);

TableEntity secondEntity = new TableEntity(partitionKey, "rk002")
    .addProperty("Brand", "Crayola")
    .addProperty("Color", "Blue");

TableEntity entityToUpdate = new TableEntity(partitionKey, "rk003")
    .addProperty("Brand", "Crayola")
    .addProperty("Color", "Red");

TableEntity entityToDelete = new TableEntity(partitionKey, "rk004")
    .addProperty("Brand", "Crayola")
    .addProperty("Color", "Green");

transactionActions.add(new TableTransactionAction(TableTransactionActionType.CREATE, firstEntity));
transactionActions.add(new TableTransactionAction(TableTransactionActionType.CREATE, secondEntity));
transactionActions.add(new TableTransactionAction(TableTransactionActionType.UPDATE_MERGE, entityToUpdate));
transactionActions.add(new TableTransactionAction(TableTransactionActionType.DELETE, entityToDelete));

TableTransactionResult tableTransactionResult = tableClient.submitTransaction(transactionActions);

JavaScript

// See authenticationMethods sample for other options of creating a new client
const client = TableClient.fromConnectionString("<your-connection-string>", "tableName");
const partitionKey = "Stationery";

// Create a transaction
const transaction = new TableTransaction();

// Add actions to the transaction
transaction.createEntity({
  partitionKey,
  rowKey: "A1",
  name: "Marker Set",
  price: 5.0,
  quantity: 21
});
transaction.createEntity({
  partitionKey,
  rowKey: "A2",
  name: "Pen Set",
  price: 2.0,
  quantity: 6
});
transaction.createEntity({
  partitionKey,
  rowKey: "A3",
  name: "Pencil",
  price: 1.5,
  quantity: 100
});

// Submit the transaction using the actions list built by the helper
const transactionResult = await client.submitTransaction(transaction.actions);

Conclusion

We hope this was a valuable technical introduction to Azure Tables and what you can use today to build and ship rich experiences.

If you want to learn more and get started, here are some quick links to our official documentation:

References

Azure SDK Blog Contributions

Thank you for reading this Azure SDK blog post! We hope that you learned something new and welcome you to share this post. We are open to Azure SDK blog contributions. Please contact us at azsdkblog@microsoft.com with your topic and we’ll get you set up as a guest blogger.

2 comments

Comments are closed. Login to edit/delete your existing comments

  • Steffen Mangold

    Since years I would love to use it to store mass data. But still, because there is no delete protection or backup function for Azure Table storage, it’s not useable in production. Cosmos DB is still way to expensive to store TBs of data. Table Storage would be the perfect time series serving layer for use, but without backup… 😰 And I do not expect that to change because there is no evolution for Table storage since years.

  • Davor Pihač

    Dear Sean, thank you for the detailed examples and the introduction. The choice of the library name is somewhat unfortunate – there is already a DataTable class in System.Data namespace and this might just lead to confusion. I would also like to point out that in the post you are referring to a .NET as a language. This is clearly wrong as .NET is not a language. All the examples are given in the C# and .NET is mentioned in several places as being a language. Hope this is understood as a constructive criticism 😉