Watch the recording of this lesson on YouTube 🎥.
The goal of this lesson is to learn how to table input and output bindings work.
This lessons consists of the following exercises:
📝 Tip - If you're stuck at any point you can have a look at the source code in this repository.
📝 Tip - If you have questions or suggestions about this lesson, feel free to create a Lesson Q&A discussion here on GitHub.
Prerequisite | Exercise |
---|---|
Azure Storage Emulator or Storage account in Azure | 1-5 |
Azure Storage Explorer | 1-5 |
VSCode | 2-5 |
VSCode AzureFunctions extension | 2-5 |
Azure Functions Core Tools | 2-5 |
RESTClient for VSCode | 2-5 |
See the prerequisites page for more details.
In this exercise we'll look into storage emulation and the Azure Storage Explorer to see how you can interact with tables and entities.
-
Make sure that the storage emulator is running and open the Azure Storage Explorer.
-
Navigate to
Storage Accounts
->(Emulator - Default Ports)(Key)
->Tables
. -
Right-click
Tables
and selectCreate Table
. -
Type a name for the table:
players
-
Select the new table.
🔎 Observation - Now you see the contents of the table (which is still empty). In the top menu you see actions you can perform on the table or its records (entities).
-
Try adding a record to the table, you can use the following data:
-
PartitionKey: United Kingdom (string)
-
RowKey: 52a3be19-dc1d-4f29-84a6-1013fcfddfa3 (string)
-
Id: 52a3be19-dc1d-4f29-84a6-1013fcfddfa3 (string)
-
NickName: Ada (string)
-
Email: [email protected] (string)
-
Region: United Kingdom (string)
🔎 Observation You'll see that the
PartitionKey
andRowKey
values are also available in theRegion
andId
fields respectively. This type of 'entity modelling' is not required for Table entities. This is just they way we prefer to structure our data. We identify the fields in the business domain we want to use as keys and keep the original fields and their values as is. An alternative would be to only keep thePartitionKey
andRowKey
values and not include theId
andRegion
fields. But then you need a bit more mapping in your domain classes to map to theId
andRegion
fields again.📝 Tip - use the
Add Property
button to add new fields to the entity.
-
In this exercise, we'll be creating an HttpTrigger function and use the Table output binding with a type based on TableEntity
in order to put player data in the players
table.
-
In VSCode, create a new HTTP Trigger Function App with the following settings:
- Location: AzureFunctions.Table
- Language: C#
- Template: HttpTrigger
- Function name: StorePlayerReturnAttributeTableOutput
- Namespace: AzureFunctionsUniversity.Demo
- AccessRights: Function
-
Once the Function App is generated, add a reference to these NuGet packages:
Microsoft.Azure.WebJobs.Extensions.Storage
. This allows us to use bindings for Blobs, Tables and Queues.Microsoft.Azure.Cosmos.Table
. This allows us to use theTableEntity
type as a basis for our customPlayerEntity
type.
📝 Tip - One way to install packages is to use the NuGet Package Manager VSCode extension:
- Run
NuGet Package Manager: Add new Package
in the Command Palette (CTRL+SHIFT+P). - Type the name of the package (e.g.
Microsoft.Azure.WebJobs.Extensions.Storage
). - Select the most recent (non-preview) version of the package.
-
We'll be working with a
PlayerEntity
type, similar to thePlayer
type used in the Blob and Queue lessons. However, that exact same class can't be used here since we need to use thePartitionKey
andRowKey
properties Table Storage requires.-
Create a new file to the project, called
PlayerEntity.cs
. -
Copy/paste this content into it.
🔎 Observation - Look at the
PlayerEntity
class. Notice that it inherits fromTableEntity
. This type comes from theMicrosoft.Azure.Cosmos.Table
NuGet package. Entities require a default, public parameterless, constructor (for proper (de)serialization). If you don't provide one you'll get errors such as;Table entity types must provide a default constructor.
. In addition to the default constructor there is a constructor which sets all properties including thePartitionKey
andRowKey
based on the region and ID of the player. The keys are passed to the base class, theTableEntity
. Finally, note that there is aSetKeys()
method. This method will be used in the functions we'll write in this lesson, in order to set thePartitionKey
andRowKey
. We're not constructing a newPlayerEntity
using the constructor, but updating an incomplete entity, which we'll receive from the HTTP request body.
-
-
Now update the function method HttpTrigger argument so it looks like this:
[HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Post), Route = null)] PlayerEntity playerEntity)
🔎 Observation - We expect that a
PlayerEntity
type will be posted to this HTTP endpoint. Assume that thePartitionKey
andRowKey
properties are not provided as part of the JSON object in the request. We'll deal with those later. -
We haven't specified the table name yet. Lets add a new file, called
TableConfig.cs
and copy the following into the file:namespace AzureFunctionsUniversity.Table { public static class TableConfig { public const string Table = "players"; } }
🔎 Observation - Now we can refer to the table name by using
TableConfig.Table
. -
Back in the function class, add the following return attribute just below the
FunctionName
attribute:[return: Table(TableConfig.Table)]
🔎 Observation - We've now defined that we return the output from the function to a table which name is configured in the
TableConfig
class.🔎 Observation - Notice that we're not specifying the
Connection
property for theTable
binding. This means the storage connection of the Function App itself is used for Table storage. It now uses the"AzureWebJobsStorage"
setting in thelocal.settings.json
file. The value of this setting should be:"UseDevelopmentStorage=true"
when emulated storage is used. When an Azure Storage Account is used this value should contain the connection string to that Storage Account. For production workloads it's recommended to use a separate Storage Account for your data. -
Remove the entire content of the function method and replace it with these two lines:
playerEntity.SetKeys() return playerEntity;
❔ Question - We're calling the
SetKeys()
method on thePlayerEntity
class. Why are we doing this before we return the entity to the table? -
Verify that the entire function method looks as follows:
[FunctionName(nameof(StorePlayerReturnAttributeTableOutput))] [return: Table(TableConfig.Table)] public static PlayerEntity Run( [HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Post), Route = null)] PlayerEntity playerEntity) { playerEntity.SetKeys() return playerEntity; }
-
Ensure that the storage emulator is started. Then build & run the
AzureFunctions.Table
Function App.📝 Tip - When you see an error like this:
Microsoft.Azure.Storage.Common: No connection could be made because the target machine actively refused it.
that means that the Storage Emulator has not been started successfully and no connection can be made to it. Check the app settings in the local.settings.json and (re)start the emulated storage. -
Do a POST request to the function endpoint:
POST http://localhost:7071/api/StorePlayerReturnAttributeTableOutput Content-Type: application/json { "id": "{{$guid}}", "nickName" : "Frances", "email" : "[email protected]", "region" : "United States of America" }
❔ Question - Look at the Azure Functions console output. Is the function executed without errors?
❔ Question - Using the Azure Storage Explorer, check if there's a new entity in the
players
table. If so, click on the entity and inspect its properties.
In this exercise, we'll be adding an HttpTrigger function and use the Table output binding with the IAsyncCollector<PlayerEntity>
output type in order to store multiple player entities in the players
table when the HTTP request contains an array of Player
objects.
-
Create a copy of the
StorePlayerReturnAttributeTableOutput.cs
file and rename the file, the class and the function toStorePlayersWithAsyncCollectorTableOutput.cs
. -
We won't be using the return attribute in this function so remove the line with
[return: Table(TableConfig.Table)]
. -
Change the
Run
method signature from:public static PlayerEntity Run
to
public static async Task<IActionResult> Run
-
Since the method needs to work with an array of
PlayerEntity
elements, change the input type from:PlayerEntity playerEntity
to
PlayerEntity[] playerEntities
-
Add the following
Table
output binding to the method:[Table(TableConfig.Table)] IAsyncCollector<PlayerEntity> collector
📝 Tip - The
IAsyncCollector<T>
andICollector<T>
interfaces are supported by several output bindings such as Queue, Table, ServiceBus, and EventHubs. When this interface is used, items are added to the (in-memory) collector and not directly to the target service behind the output binding. Once the collector is flushed, either using a direct method call or automatically when the function completes, the items in the collector are transferred. -
Replace the content of the
Run
method with this code:foreach (var playerEntity in playerEntities) { playerEntity.SetKeys(); await collector.AddAsync(playerEntity); } return new AcceptedResult();
-
Verify that the entire function looks like this now:
public static class StorePlayersWithAsyncCollectorTableOutput { [FunctionName(nameof(StorePlayersWithAsyncCollectorTableOutput))] public static async Task<IActionResult> Run( [HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Post), Route = null)] PlayerEntity[] playerEntities, [Table(TableConfig.Table)] IAsyncCollector<PlayerEntity> collector) { foreach (var playerEntity in playerEntities) { playerEntity.SetKeys(); await collector.AddAsync(playerEntity); } return new AcceptedResult(); } }
-
Ensure that the storage emulator is started. Then build & run the
AzureFunctions.Table
Function App. -
Do a POST request with an array of players to the function endpoint:
POST http://localhost:7071/api/StorePlayersWithAsyncCollectorTableOutput Content-Type: application/json [ { "id": "{{$guid}}", "nickName" : "Grace", "email" : "[email protected]", "region" : "United States of America" }, { "id": "{{$guid}}", "nickName" : "Margaret", "email" : "[email protected]", "region" : "United States of America" }, { "id": "{{$guid}}", "nickName" : "Mary", "email" : "[email protected]", "region" : "United States of America" } ]
❔ Question - Look at the Azure Functions console output. Is the function executed without errors?
❔ Question - Using the Azure Storage Explorer, are there several new entities in the
players
table?
In this exercise, we'll be adding an HttpTrigger function and use the Table input binding with the PlayerEntity
type in order to retrieve one player entity from the players
table. We'll be doing a point query, which means we use both the PartitionKey
and RowKey
in order ot retrieve a single entity from the table. In this case we'll provide the player region (PartitionKey
) and the player ID (RowKey
), both will be part of the route.
-
Create a copy of the
StorePlayerReturnAttributeTableOutput.cs
file and rename the file, the class and the function toGetPlayerByRegionAndIdCloudTableInput.cs
. -
We won't be using the return attribute in this function so remove the line with
[return: Table(TableConfig.Table)]
. -
Update the HttpTrigger attribute as follows:
[HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Get), Route = "GetPlayerByRegionAndIdTableInput/{region}/{id}")] HttpRequest request,
🔎 Observation - Note that Route parameter contains {region} and {id} expressions.
-
Add the following Table input binding as the final parameter of the method:
[Table( TableConfig.Table, "{region}", "{id}")] PlayerEntity playerEntity)
🔎 Observation - Note that the Table input binding uses the exact same {region} and {id} expressions as the HttpTrigger. This will result in a point query on the table that returns a single entity.
-
Update the body of the function with:
return new OkObjectResult(playerEntity);
-
Verify that the entire function looks like this:
[FunctionName(nameof(GetPlayerByRegionAndIdTableInput))] public static IActionResult Run( [HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Get), Route = "GetPlayerByRegionAndIdTableInput/{region}/{id}")] HttpRequest request, [Table( TableConfig.Table, "{region}", "{id}")] PlayerEntity playerEntity) { return new OkObjectResult(playerEntity); }
-
Ensure that the storage emulator is started. Then build & run the
AzureFunctions.Table
Function App. -
Ensure that there's at least one entity present in the
players
Table. Copy thePartitionKey
andRowKey
for that entity to use in in the next step. -
Do a GET request to the endpoint and update the
PARTITION_KEY
andROW_KEY
fields with the values from the previous step:GET http://localhost:7071/api/GetPlayerByRegionAndIdTableInput/PARTITION_KEY/ROW_KEY
Example
GET http://localhost:7071/api/GetPlayerByRegionAndIdTableInput/United%20States%20of%20America/6449f9a2-56be-4f7c-a8ee-02603bb7625c
❔ Question - Does the function run without errors? Do you get the expected
PlayerEntity
back?
In this exercise we'll create an HttpTrigger function which returns multiple PlayerEntity
objects from the players
table using a Table input binding based on the CloudTable
type. The body of the function will use a TableQuery
that uses information based on parameters from the HTTP query string (region and nickname).
-
Create a copy of the
GetPlayerByRegionAndIdTableInput.cs
file and rename the file, the class and the function toGetPlayersByRegionAndNickNameCloudTableInput.cs
. -
Update the HttpTrigger so the Route is
null
:[HttpTrigger( AuthorizationLevel.Function, nameof(HttpMethods.Get), Route = null)] HttpRequest request
-
Remove the
region
andid
parameters from the method. We'll be using the query string parameters this time. -
Update the Table input binding to this:
[Table(TableConfig.Table)] CloudTable cloudTable
🔎 Observation - The Table binding only uses the table name now. The type the binding is using is
CloudTable
and comes from theMicrosoft.Azure.Cosmos.Table
NuGet package. The CloudTable exposes a lot of methods to interact with a Table in either TableStorage or CosmosDB Tables, have a look! -
Replace the body of the function method with:
string region = request.Query["region"]; string nickName = request.Query["nickName"]; var regionAndNickNameFilter = new TableQuery<PlayerEntity>() .Where( TableQuery.CombineFilters( TableQuery.GenerateFilterCondition( nameof(PlayerEntity.PartitionKey), QueryComparisons.Equal, region), TableOperators.And, TableQuery.GenerateFilterCondition( nameof(PlayerEntity.NickName), QueryComparisons.Equal, nickName))); var playerEntities = cloudTable.ExecuteQuery<PlayerEntity>(regionAndNickNameFilter); return new OkObjectResult(playerEntities);
🔎 Observation - Note that the region and nickName are retrieved from the query string of the HTTP request.
🔎 Observation - Note that a
TableQuery<PlayerEntity>
is created with filter conditions based on thePartitionKey
and theNickName
properties of aPlayerEntity
. The query is executed on theCloudTable
type that is tied to the Table input binding.❔ Question - Look into the
TableQuery
class. What other methods does it support?❔ Question - Look into the
QueryComparisons
class. What other constants does it have?❔ Question - Look into the
TableOperators
class. What other constants does it have? -
Ensure that the storage emulator is started. Then build & run the
AzureFunctions.Table
Function App. -
Ensure that there are several entities present in the
players
Table. Copy thePartitionKey
andNickName
of an entity you want to return from the function. -
Do a GET request to the endpoint and update the
PARTITION_KEY
andNICK_NAME
fields with the values from the previous step:GET http://localhost:7071/api/GetPlayersByRegionAndNickNameCloudTableInput ?region=PARTITION_KEY &nickName=NICK_NAME
Example
GET http://localhost:7071/api/GetPlayersByRegionAndNickNameCloudTableInput ?region=United States of America &nickName=Mary
❔ Question - Did the function run without errors? Are the correct entities returned?
Here is the assignment for this lesson.
For more info about the Table Trigger and binding have a look at the official Azure Functions Table Storage Bindings documentation. For details on Azure Table Storage look at this Table Storage Overview and this Table Storage Design Guide.
We love to hear from you! Was this lesson useful to you? Is anything missing? Let us know in a Feedback discussion post here on GitHub.