DynamoDB Interfaces Overview with .NET

Mark Perry
5 min readOct 29, 2020

--

A couple of years ago I started a new job, I had plenty of experience working with SQL and Entity Framework, then I found myself having to rapidly learn DynamoDB and getting very confused in the process.

Most developers who come from a SQL/EF background will be used to having DB models in the application code, using LINQ to retrieve data, then mapping it to the relevant application or UI model. I know because that’s what I used to do. However DynamoDB is a a bit different, it’s a NoSQL database, none of the tools I was familiar with were of any use! There’s no ORM, and the query language didn’t resemble anything I had ever used before! I no longer had the power of LINQ to Entities, I couldn’t order by any column at the database level, and I couldn’t join tables. Of course the differences go far beyond this, but that’s another article in itself.

The AWS documentation is great, but I still found myself using Google more than I would care to admit. I found plenty of samples that I thought I could follow, however they rarely worked with my existing code. I’m ashamed to admit that it took me a while to realise why, and here it is…DynamoDB has 3 different interfaces; it wasn’t always obvious which one was being used in the examples, and I didn’t know enough about them to fully understand the difference.
Hopefully, this explanation of each interface will start to solve that confusion!

DynamoDB Interface Architecture
Image sourced from AWS documentation

Low Level Interface (Amazon.DynamoDBv2)

All the AWS SDKs offer this level of interface for working with DynamoDB. It’s the most complex of the interfaces offered by the AWS SDK, and requires the most code to use, but it is also the most powerful once you know how to use it.
Working with table, items and indexes at this level will require more code, and in most places you will need to be explicit about the data types (hence why it can be more complex), but it does give you the benefit of being able to work with all the DynamoDB APIs.

var putResponse = await _client.PutItemAsync(new PutItemRequest
{
TableName = _tableName,
Item = new Dictionary<string, AttributeValue>
{
{
"id", new AttributeValue
{
S = person.Id.ToString()
}
},
{
"name", new AttributeValue
{
S = person.Name
}
},
{
"age", new AttributeValue
{
N = person.Age.ToString()
}
}
}
});
var item = await _client.GetItemAsync(new GetItemRequest(_tableName,new Dictionary<string, AttributeValue>
{
{"id", new AttributeValue(id.ToString())}
}));

var personsName = item.Item["name"].S;
var personsAge = item.Item["age"].N;

As we can see above, the interface is expecting an explicit definition of the type of data when using PUT, and when we retrieve the item we need to access the attributes value though the same property. These types, (S
, N etc) tell DynamoDB how to store the data in the table. More information on the AttributeValue class and it data types can be found here.

Document Model (Amazon.DynamoDBv2.DocumentModel)

This is the ‘mid-level’ interface, and is available in the AWS SDKs for Java, .NET, node.js and also javascript in the browser. The interface is simpler to use than the Low Level Interface. Table and Document objects represent the table and its rows respectively.
Each attributes is returned as a DynamoDBEntry, which is the abstract base class for DynamoDBBool, DynamoDBList, DynamoDBNull, Primitive and PrimitiveList. These types make it easier to get to the underlying value than the AttributeValue class provided by the Low Level Interface, but won’t map directly to your applications objects without some extra effort.

var putResponse = await _table.PutItemAsync(new Document
{
{"id", new Primitive(person.Id.ToString())},
{"name", new Primitive(person.Name)},
{"age", new Primitive(person.Age.ToString())}
});
var item = await _table.GetItemAsync(new Primitive(id.ToString()));
var personsName = item["name"];
var personsAge = item["age"];

The Document Model interface makes things more concise, but we still have to do some mapping between Primitive (or other types as listed above), and our .NET object. Also, should we wish to do any operations on the data we retrieve we would need to cast, or explicitly convert the value to the required type.

Object Persistence Model (Amazon.DynamoDBv2.DataModel)

This is the highest level of interface, but is only available for the Java and .NET AWS SDKs. This model will be more familiar to developers who are used to working with ORM systems such as Entity Framework. The database is accessed through the DynamoDBContext class and allows us to store .NET models in the database with minimum effort. The SDK will handle all the conversions between .NET objects and DynamoDB attributes. However, the DynamoDBContext is much more constrained in its functionality and large sections of the APIs will not be available.

[DynamoDBTable("person")]
public class Person
{
[DynamoDBHashKey("id")]
public Guid Id { get; set; }

[DynamoDBProperty("name")]
public string Name { get; set; }

[DynamoDBProperty("age")]
public int Age { get; set; }
}
await _context.SaveAsync(person);
var item = await _context.LoadAsync<Person>(id);

The Object Persistence Model is definitely the easiest to use, and tidiest with regards to length of code and readability. The use of theDynamoDBProperty attribute is not required but can be useful to map .NET property to DynamoDB attributes that use a different name. Without the attribute then the DynamoDBContext will attempt to match the names using a case-sensitive comparison. The DynamoDBHashKey is required.

Which Interface Should I Use?

As I mentioned in the introduction, this can be a tricky question to answer if you are not used to working DynamoDB. With 3 levels available it is often not very obvious where to start. The first question is ‘what language am I using?’. Since the higher level interfaces are only available for a subset of the AWS SDKs this can quite often determine the level of interface you should use.
If you are using Java or .NET then you have a lot more choice and it will depend on the operations you need to support. If you need complex granular operations (Conditional PUT for example) then you need the low level interface, but if you are working on a simple CRUD application then the Object Persistence Model is probably the one for you.
The key point to note is that you are not constrained to picking an interface and sticking with it, you can easily mix and match as required.

A full working example using the code in my examples can be found on my Github.

--

--

No responses yet