Enum ToString(), Caching for Performance

Full source code available here.

A while ago I was working on a program that had to convert enums values to strings as it saved data.

When I removed the enum value from the data that was saved it went noticeably faster. Did a little digging and it seems that ToString() on the enum was using reflection every time it was called, even if it was same enum and member that was being saved.

Here is an extension method that stores the string value of the enum so it gets called only once and the rest of the time you are looking up a dictionary to read the string value from.

public static class EnumExtensions
{
    private static Dictionary<Enum, string> enumStringValues = new Dictionary<Enum, string>();
    public static string ToStringCached(this Enum myEnum)
    {   
        string textValue;
        if (enumStringValues.TryGetValue(myEnum, out textValue))
        {
            return textValue;
        }
        else
        {
            textValue = myEnum.ToString();
            enumStringValues[myEnum] = textValue;
            return textValue;
        }
    }
}

This works fine even if you two enums that share a member names, for example –

public enum Movement
{
    Walk = 1,
    March = 2,
    Run = 3,
    Crawl = 4,
}

and

public enum Month
{
    January = 1,
    February = 2,
    March = 3,
    April = 4,
    //snip...
}

To try this out –

static void Main(string[] args)
{
    var marching =  Movement.March; 
    
    var monthOfMarch = Month.March;
    var monthOfApril = Month.April;

    Console.WriteLine(marching.ToStringCached()); // this will store it in the dictionary
    Console.WriteLine(marching.ToStringCached()); // this will retrieve it from the dictionary
    
    Console.WriteLine(monthOfMarch.ToStringCached()); // this will store it in the dictionary
    Console.WriteLine(monthOfMarch.ToStringCached()); // this will retrieve it from the dictionary

    Console.WriteLine(monthOfApril.ToStringCached()); // this will store it in the dictionary
    Console.WriteLine(monthOfApril.ToStringCached()); // this will retrieve it from the dictionary
}

Inside the dictionary you end up with three entries.

[0] [KeyValuePair]:{[March, March]}
Key [Enum {Movement}]:March
Value [string]:"March"

[1] [KeyValuePair]:{[March, March]}
Key [Enum {Month}]:March
Value [string]:"March"

[2] [KeyValuePair]:{[April, April]}
Key [Enum {Month}]:April
Value [string]:"April"

Full source code available here.

DynamoDb, Reading and Writing Data with .Net Core – Part 1

Full source code available here.

A few weeks ago I started playing with DynamoDb in a .NET application. I read through the AWS documentation but felt it was incomplete and a little out of date. This made it quite hard to figure out the “right” way of using the AWS DynamoDb libraries. For example, there is no good example of using dependency injection to pass a DynamoDb client into a controller, and no guidance on whether that client should be request, transient of singleton scoped.
https://github.com/localstack/awscli-local
In this and following posts I’m going to describe approaches that work, they may not be “right” either.
This post is will show how to read and write from DynamoDb using the document interface and pass in a DynamoDb client to controller

Getting Started

Getting started with AWS can feel like a bit of a hurdle (especially due to credential complications), and a good way to get your feet wet is to use localstack – https://github.com/localstack/localstack, consider installing https://github.com/localstack/awscli-local too.

I’m not going to describe how to get localstack running, I’m going to assume you have done that or you have an AWS account and you know how to set the required credentials.

Once you have localstack installed or you AWS account working, run the following to create the DynamoDB table.

aws --endpoint-url=http://localhost:4569 dynamodb create-table --table-name People  --attribute-definitions AttributeName=PersonId,AttributeType=N --key-schema AttributeName=PersonId,KeyType=HASH --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1

You can add data to the table with the following –

aws --endpoint-url=http://localhost:4569 dynamodb put-item --table-name People  --item '{"PersonId":{"N":"1"},"State":{"S":"MA"}, "FirstName": {"S":"Alice"}, "LastName": {"S":"Andrews"}}'
aws --endpoint-url=http://localhost:4569 dynamodb put-item --table-name People  --item '{"PersonId":{"N":"2"},"State":{"S":"MA"}, "FirstName": {"S":"Ben"}, "LastName": {"S":"Bradley"}}'
aws --endpoint-url=http://localhost:4569 dynamodb put-item --table-name People  --item '{"PersonId":{"N":"3"},"State":{"S":"MA"}, "FirstName": {"S":"Colin"}, "LastName": {"S":"Connor"}}'

To see all the items you just stored –

aws --endpoint-url=http://localhost:4569 dynamodb scan --table-name People

The Web API application

I’m going to show you how to retrieve and item by an id and to store a new item using the document interface, in the next post I’ll show how to do the same with the object interface.

But first things first, configuration.

Open the appsettings.json file and replace what’s there with –

{
  "AWS": {
    "Profile": "localstack-test-profile",
    "Region": "us-east-1",
    "ServiceURL": "http://localhost:4566"
  }
}

Add the AWSSDK.DynamoDBv2 nuget package to the project.

In Startup.cs add a using Amazon.DynamoDBv2;

In ConfigureServices() add the following –

public void ConfigureServices(IServiceCollection services)
{
    var awsOptions = Configuration.GetAWSOptions();

    services.AddDefaultAWSOptions(awsOptions);
    services.AddAWSService<IAmazonDynamoDB>();
    services.AddControllers();
}

Inside the controller I’m going to use an AmazonDynmoDbClient to execute calls against DybamoDb, but the SDK doesn’t seem to provide a way to inject one directly, so I came up with a workaround.

//snip...
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;
//snip.. 

public class PersonController : ControllerBase
{
    private readonly AmazonDynamoDBClient _amazonDynamoDbClient;
    public PersonController(IAmazonDynamoDB amazonDynamoDB)
    {
        _amazonDynamoDbClient = (AmazonDynamoDBClient) amazonDynamoDB;
    }
    //snip..

Line 8 declares an AmazonDynamoDBClient.
Line 9, the constructor takes an IAmazonDynamoDB as a parameter.
Line 11 casts this IAmazonDynamoDB to an AmazonDynamoDBClient.
Again, I don’t know if this is the best approach, but it works fine.

Here is how to get an item from the people table.

[HttpGet("{personId}")]
public async Task<IActionResult> Get(int personId)
{
    Table people = Table.LoadTable(_amazonDynamoDbClient, "People");
    var person =  JsonSerializer.Deserialize<Person> ((await people.GetItemAsync(personId)).ToJson());
    return Ok(person);
}

This is how you add an item to the People table.

[HttpPost]
public async Task<IActionResult> Post(Person person)
{
    Table people = Table.LoadTable(_amazonDynamoDbClient, "People");
    
    var document = new Document();
    document["PersonId"] = person.PersonId;
    document["State"] = person.State;
    document["FirstName"] = person.FirstName;
    document["LastName"] = person.LastName;
    await people.PutItemAsync(document);
    
    return Created("", document.ToJson());
}

I’m not a fan of all the hardcoded attributes for properties of the person, but this is a way of doing it.
Another approach is to use the object model, and I’ll show that in a later blog post.

To exercise the GET method open your browser and go to http://localhost:5000/people/1.

To exercise the POST you can use Fiddler, Curl, Postman or the very nice extension for REST Client extension for Visual Studio Code.

If you are using that extension, here is the request –

POST http://localhost:5000/person HTTP/1.1
content-type: application/json

{
    "personId": 6,
    "state": "MA",
    "firstName": "Frank",
    "lastName" : "Freeley"
}

Full source code available here.