DynamoSharp 0.1.2

dotnet add package DynamoSharp --version 0.1.2
                    
NuGet\Install-Package DynamoSharp -Version 0.1.2
                    
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="DynamoSharp" Version="0.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DynamoSharp" Version="0.1.2" />
                    
Directory.Packages.props
<PackageReference Include="DynamoSharp" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DynamoSharp --version 0.1.2
                    
#r "nuget: DynamoSharp, 0.1.2"
                    
#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.
#:package DynamoSharp@0.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DynamoSharp&version=0.1.2
                    
Install as a Cake Addin
#tool nuget:?package=DynamoSharp&version=0.1.2
                    
Install as a Cake Tool

DynamoSharp

DynamoSharp is an ORM (Object-Relational Mapping) library for .NET that simplifies interaction with Amazon DynamoDB, with a focus on Single-Table Design. It offers a simple and efficient way to map data models to DynamoDB tables and perform CRUD (Create, Read, Update, Delete) operations intuitively, as well as tracking changes in entities.

Features:

  • Model Mapping: Support for defining primary and secondary keys, global indexes, and relationships between entities.

  • Efficient Queries: Provides a query builder that allows complex searches using partition and sort keys, as well as secondary indexes.

  • Relationship Support: Handles one-to-many and many-to-many relationships between entities.

  • Focus on Single Table Design: Optimizes the use of DynamoDB through single table design, allowing multiple entity types to be stored in a single table and enabling efficient queries.

  • Entity Tracking: Automatically tracks changes in entities to help detect and manage data updates effectively.

  • Version control with Optimistic Locking: Makes sure that updates do not overwrite each other by checking the version of the data before saving changes.

  • Retry Strategies: Automatically handles retries for transient DynamoDB errors such as InternalServerError, LimitExceeded, ProvisionedThroughputExceeded, RequestLimitExceeded, ServiceUnavailable, and Throttling. This feature ensures reliability and removes the need to implement retry logic in your business layer.

DynamoSharp simplifies the use of DynamoDB in .NET applications, providing an abstraction layer that allows developers to focus on business logic without worrying about the details of database interaction, while taking advantage of the benefits of single table design to optimize performance and data efficiency.

Table of Contents

Installation

dotnet add package DynamoSharp

Configuration


builder.Services.AddDynamoSharp(); //Add internal dependencies
builder.Services.AddDynamoSharpContext<AppContext>(
    new TableSchema.Builder()
        .WithTableName("dynamosharp")
        .WithPartitionKeyName("PK")
        .WithSortKeyName("SK")
        .AddGlobalSecondaryIndex("GSI1PK-GSI1SK-index", "GSI1PK", "GSI1SK")
        .Build()
);

Implementation

public class ModelItem { }

public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<ModelItem> Items { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter,TableSchema tableSchema) 
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    public override void OnModelCreating(IModelBuilder modelBuilder)
    { }
}

How to map

Simple Primary Key

Model
public class User
{
    // IMPORTANT
    // The model must have a property named Id
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public SubscriptionLevel SubscriptionLevel { get; set; }

    ...
}
Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<User> Users { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema) 
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    /*
    Partition Key and Sort Key are not defined,
    so they will be automatically generated by the library.

    Example Partition Key:
    USER#1

    Example Sort Key:
    USER#1
    */
}
Result
PartitionKey SortKey Id Name Email SubscriptionLevel
USER#1 USER#1 1 Chris chris@example.com Ultimate

Custom Primary Key

Model
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public SubscriptionLevel SubscriptionLevel { get; set; }

    ...
}
Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<User> Users { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema) 
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    public override void OnModelCreating(IModelBuilder modelBuilder)
    {
        // Example Partition Key: USR#chris@example.com
        modelBuilder.Entity<User>()
            .HasPartitionKey(u => u.Email, "USR");

        // Example Sort Key: NAME#Chris
        modelBuilder.Entity<User>()
            .HasSortKey(u => u.Name, "NAME");
    }
}
Result
PartitionKey SortKey Id Name Email SubscriptionLevel
USR#chris@example.com NAME#Chris 1 Chris chris@example.com Ultimate

Global Secondary Index Primary Key

Model
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }

    ...
}
Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<User> Users { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema) 
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    public override void OnModelCreating(IModelBuilder modelBuilder)
    {
        /*
        Partition Key and Sort Key are not defined,
        so they will be automatically generated by the library.

        - User
            Example Partition Key:
            USER#1

            Example Sort Key:
            USER#1

        */

        // Example Global Secondary Index Partition Key: 
        // USER#D1ADDCA5-2B7B-4DE5-A221-C626C0A677F9
        modelBuilder.Entity<User>()
            .HasGlobalSecondaryIndexPartitionKey("GSI1PK", u => u.Id, "USER");

        // Example Global Secondary Index Sort Key:
        // EMAIL#chris@example.com
        modelBuilder.Entity<User>()
            .HasGlobalSecondaryIndexSortKey("GSI1SK", o => u.Email, "EMAIL");
    }
}
Result
PartitionKey SortKey Id Name Email GSI1PK GSI1SK
USER#1 USER#1 1 Chris chris@example.com USER#1 EMAIL#chris@example.com

Sparse Index

Sparse indexes to provide a global filter on an item type.

Model
public class Organization
{
    public string Name { get; private set; }
    public SubscriptionLevel SubscriptionLevel { get; private set; }
    public List<User> Users { get; private set; }

    ...
}

public class User
{
    public string Name { get; private set; }
    public SubscriptionLevel SubscriptionLevel { get; private set; }

    ...
}

public enum SubscriptionLevel
{
    Admin,
    Member,
    Pro,
    Enterprise
}
Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<Organization> Organizations { get; private set; } = null!;
    public IDynamoDbSet<User> Users { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema) 
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    public override void OnModelCreating(IModelBuilder modelBuilder)
    {
        // Example Partition Key: ORG#DynamoSharp
        modelBuilder.Entity<Organization>()
            .HasPartitionKey(o => o.Name, "ORG");

        // Example Sort Key: ORG#DynamoSharp
        modelBuilder.Entity<Organization>()
            .HasSortKey(u => u.Name, "ORG");

        // Example Global Secondary Index Partition Key: ORGANIZATIONS
        modelBuilder.Entity<Organization>()
            .HasGlobalSecondaryIndexPartitionKey("GSI1PK", "ORGANIZATIONS"); // <- SPARSE INDEX

        // Example Global Secondary Index Sort Key: ORG#DynamoSharp
        modelBuilder.Entity<Organization>()
            .HasGlobalSecondaryIndexSortKey("GSI1SK", o => o.Name);

        modelBuilder.Entity<Organization>()
            .HasOneToMany(u => u.Users);


        // Example Sort Key: USER#Chris
        modelBuilder.Entity<User>()
            .HasSortKey(u => u.Name, "USER");
    }
}
Result
PartitionKey SortKey Name SubscriptionLevel GSI1PK GSI1SK
ORG#Amazon ORG#Amazon Amazon Admin ORGANIZATIONS Amazon
ORG#DynamoSharp ORG#DynamoSharp DynamoSharp Admin ORGANIZATIONS DynamoSharp
ORG#Amazon USER#Chris Chris Member

Primary Key with nested properties

Model
public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
    public string Country { get; set; }

    ....
}

public class Store
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
    public Address Address { get; set; }

    ...
}
Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<User> Users { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema) 
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    public override void OnModelCreating(
        IModelBuilder modelBuilder)
    {
        // Example Partition Key: COUNTRY#Mexico
        modelBuilder.Entity<Store>()
            .HasPartitionKey(u => u.Country, "COUNTRY");

        // Example Sort Key: STATE#Tabasco
        modelBuilder.Entity<Store>()
            .HasSortKey(u => u.State, "STATE");

        // Example Global Secondary Index Partition Key: 
        // CITY#Villahermosa
        modelBuilder.Entity<Store>()
            .HasGlobalSecondaryIndexPartitionKey("GSI1PK", u => u.City, "CITY");

        // Example Global Secondary Index Sort Key:
        // ZIPCODE#00000
        modelBuilder.Entity<Store>()
            .HasGlobalSecondaryIndexSortKey("GSI1SK", o => u.ZipCode, "ZIPCODE");
    }
}
Result
PartitionKey SortKey Id Name Phone Email Address GSI1PK GSI1SK
COUNTRY#Mexico STATE#Tabasco 1 Tacos El Guero 1234567890 example@example.com { ... } CITY#Villahermosax ZIPCODE#00000x

Primary Key with hierarchical data

Model
public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string ZipCode { get; set; }
    public string Country { get; set; }

    ....
}

public class Store
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
    public Address Address { get; set; }

    ...
}
Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<User> Users { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema)
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    public override void OnModelCreating(
        IModelBuilder modelBuilder)
    {
        // Example Partition Key: COUNTRY#Mexico
        modelBuilder.Entity<Store>()
            .HasPartitionKey(u => u.Country, "COUNTRY");

        // Example Sort Key: STATE#Tabasco#CITY#Villahermosa#ZIPCODE#00000
        modelBuilder.Entity<Store>()
            .HasSortKey(u => u.State, "STATE")
            .Include(s => s.Address.City, "CITY");

        // Example Global Secondary Index Partition Key: Mexico
        modelBuilder.Entity<Store>()
            .HasGlobalSecondaryIndexPartitionKey("GSI1PK", u => u.Country);

        // Example Global Secondary Index Sort Key:
        // Tabasco#Villahermosa#00000
        modelBuilder.Entity<Store>()
            .HasGlobalSecondaryIndexSortKey("GSI1SK", o => u.State)
            .Include(s => s.Address.City, "CITY")
            .Include(s => s.Address.ZipCode, "ZIPCODE");
    }
}
Result
PartitionKey SortKey Id Name Phone Email Address GSI1PK GSI1SK
COUNTRY#Mexico STATE#Tabasco#CITY#Villahermosa 1 Tacos El Guero 1234567890 example@example.com { ... } Mexico Tabasco#Villahermosa#00000

One to many

Model
public class Item
{
    public int Id { get; set; }
    public string ProductName { get; set; }
    public decimal UnitPrice { get; set; }
    public int Units { get; set; }

    ...
}

public class Order
{
    public int Id { get; set; }
    public int BuyerId { get; set; }
    public Address Address { get; set; }
    public Status Status { get; set; }
    public DateTime Date { get; set; }

    private readonly List<Item> _items = new List<Item>();
    public IReadOnlyCollection<Item> Items => _items;

    ...
}
Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<Order> Orders { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema) 
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    public override void OnModelCreating(IModelBuilder modelBuilder)
    {
        /*
        Partition Key and Sort Key are not defined,
        so they will be automatically generated by the library.

        - Order
            Example Partition Key:
            ORDER#1

            Example Sort Key:
            ORDER#1
        
        - Item
            Example Partition Key:
            ORDER#1
            
            Example Sort Key:
            ITEM#2
        */

        modelBuilder.Entity<Order>()
            .HasOneToMany(o => o.Items);
    }
}
Result
PartitionKey SortKey Id BuyerId Address Status Date ProductName UnitPrice Units
ORDER#1 ORDER#1 1 26 { ... } Shipped 2024-08-28T19:41:50.9509387-06:00
ORDER#1 ITEM#2 2 Product 1 26.0 5

One to many with custom primary key

Model
public class Organization
{
    public string Name { get; private set; }
    public SubscriptionLevel SubscriptionLevel { get; private set; }
    public List<User> Users  { get; private set; }

    ...
}

public class User
{
    public string Name { get; private set; }
    public SubscriptionLevel SubscriptionLevel { get; private set; }

    ...
}

public enum SubscriptionLevel
{
    Admin,
    Member,
    Pro,
    Enterprise
}

Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<Organization> Organizations { get; private set; } = null!;
    public IDynamoDbSet<User> Users { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapterAdapter, TableSchema tableSchema)
        : base(dynamoDbContextAdapterAdapter, tableSchema)
    { }

    public override void OnModelCreating(
        IModelBuilder modelBuilder)
    {
        // Example Partition Key: ORG#DynamoSharp
        modelBuilder.Entity<Organization>()
            .HasPartitionKey(o => o.Name, "ORG");

        // Example Sort Key: ORG#DynamoSharp
        modelBuilder.Entity<Organization>()
            .HasSortKey(u => u.Name, "ORG");

        modelBuilder.Entity<Organization>()
            .HasOneToMany(u => u.Users);

        // Example Sort Key: USER#Chris
        modelBuilder.Entity<User>()
            .HasSortKey(u => u.Name, "USER");
    }
}
Result
PartitionKey SortKey Name SubscriptionLevel
ORG#Amazon ORG#Amazon Amazon Admin
ORG#Amazon USER#Chris Chris Member

Many to many

Model
public class Movie
{
    // IMPORTANT
    // The model must have a property named Id
    public int Id { get; set; }
    public string Title { get; set; }
    public List<Performance> Actors { get; set; }

    ...
}

public class Performance
{
    // IMPORTANT
    // The model must have both Ids, in this case MovieId and ActorId
    public int MovieId { get; set; }
    public int ActorId { get; set; }
    public string MovieTitle { get; set; }
    public string ActorName { get; set; }
    public string RoleName { get; set; }

    ...
}

public partial class Actor
{
    // IMPORTANT
    // The model must have a property named Id
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Performance> Movies { get; set; }

    ...
}
Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<Movie> Movies { get; private set; } = null!;
    public IDynamoDbSet<Actor> Actors { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema) 
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    public override void OnModelCreating(IModelBuilder modelBuilder)
    {
        /*
        Partition Key and Sort Key are not defined,
        so they will be automatically generated by the library.

        - Movie
            Example Partition Key:
            MOVIE#1

            Example Sort Key:
            MOVIE#1
        
        - Actor
            Example Partition Key:
            ACTOR#2

            Example Sort Key:
            ACTOR#2

            Global Secondary Index Partition Key:
            ACTOR#2

            Global Secondary Index Sort Key:
            ACTOR#2

        - Performance
            Example Partition Key:
            MOVIE#1

            Example Sort Key:
            ACTOR#2

            Global Secondary Index Partition Key:
            ACTOR#2

            Global Secondary Index Sort Key:
            MOVIE#1
        */

        modelBuilder.Entity<Movie>()
            .HasManyToMany(m => m.Actors);

        modelBuilder.Entity<Actor>()
            .HasManyToMany(a => a.Movies);
    }
}
Result
PartitionKey SortKey Id Title MovieId ActorId MovieTitle ActorName RoleName Name GSI1PK GSI1SK
MOVIE#1 MOVIE#1 1 The Matrix
MOVIE#1 ACTOR#2 1 2 The Matrix Keanu Reeves Neo ACTOR#2 MOVIE#1
ACTOR#2 ACTOR#2 2 Keanu Reeves ACTOR#2 ACTOR#2

Many to many with custom primary key

Model
public class Movie
{
    public string Title { get; set; }
    public List<Performance> Actors { get; set; }

    ...
}

public class Performance
{
    public string MovieTitle { get; set; }
    public string ActorName { get; set; }
    public string RoleName { get; set; }

    ...
}

public partial class Actor
{
    public string Name { get; set; }
    public List<Performance> Movies { get; set; }

    ...
}
Context
public class AppContext : DynamoSharpContext
{
    public IDynamoDbSet<Movie> Movies { get; private set; } = null!;
    public IDynamoDbSet<Actor> Actors { get; private set; } = null!;

    public AppContext(IDynamoDbContextAdapter dynamoDbContextAdapter, TableSchema tableSchema) 
        : base(dynamoDbContextAdapter, tableSchema)
    { }

    public override void OnModelCreating(
        IModelBuilder modelBuilder)
    {
        MovieModelBuilder(modelBuilder);

        ActorModelBuilder(modelBuilder);

        PerformanceModelBuilder(modelBuilder);
    }

    private void MovieModelBuilder(
        IModelBuilder modelBuilder)
    {
        // Example Partition Key: MOVIE#The Matrix
        modelBuilder.Entity<Movie>()
            .HasPartitionKey(m => m.Title, "MOVIE");

        // Example Sort Key: MOVIE#The Matrix
        modelBuilder.Entity<Movie>()
            .HasSortKey(m => m.Title, "MOVIE");

        modelBuilder.Entity<Movie>()
            .HasManyToMany(m => m.Actors);
    }

    private void ActorModelBuilder(
        IModelBuilder modelBuilder)
    {
        // Example Partition Key: ACTOR#Keanu Reeves
        modelBuilder.Entity<Actor>()
            .HasPartitionKey(a => a.Name, "ACTOR");

        // Example Sort Key: ACTOR#Keanu Reeves
        modelBuilder.Entity<Actor>()
            .HasSortKey(a => a.Name, "ACTOR");

        modelBuilder.Entity<Actor>()
            .HasManyToMany(a => a.Movies);

        // Example Global Secondary Index Partition Key: ACTOR#Keanu Reeves
        modelBuilder.Entity<Actor>()
            .HasGlobalSecondaryIndexPartitionKey("GSI1PK", a => a.Name, "ACTOR");

        // Example Global Secondary Index Sort Key: ACTOR#Keanu Reeves
        modelBuilder.Entity<Actor>()
            .HasGlobalSecondaryIndexSortKey("GSI1SK", a => a.Name, "ACTOR");
    }

    private void PerformanceModelBuilder(
        IModelBuilder modelBuilder)
    {
        // Example Partition Key: MOVIE#The Matrix
        modelBuilder.Entity<Performance>()
            .HasPartitionKey(p => p.MovieTitle, "MOVIE");

        // Example Sort Key: ACTOR#Keanu Reeves
        modelBuilder.Entity<Performance>()
            .HasSortKey(p => p.ActorName, "ACTOR");

        // Example Global Secondary Index Partition Key: ACTOR#Keanu Reeves
        modelBuilder.Entity<Performance>()
            .HasGlobalSecondaryIndexPartitionKey("GSI1PK", p => p.ActorName, "ACTOR");

        // Example Global Secondary Index Sort Key: MOVIE#The Matrix
        modelBuilder.Entity<Performance>()
            .HasGlobalSecondaryIndexSortKey("GSI1SK", p => p.MovieTitle, "MOVIE");
    }
}
Result
PartitionKey SortKey Title MovieTitle ActorName RoleName Name GSI1PK GSI1SK
MOVIE#The Matrix MOVIE#The Matrix The Matrix
MOVIE#The Matrix ACTOR#Keanu Reeves The Matrix Keanu Reeves Neo ACTOR#Keanu Reeves MOVIE#The Matrix
ACTOR#Keanu Reeves ACTOR#Keanu Reeves Keanu Reeves ACTOR#Keanu Reeves ACTOR#Keanu Reeves

How to save

Save

appContext.Users.Add(newUser);
        
// Save with batch 
await _appContext.BatchWriter.SaveChangesAsync(cancellationToken);
// Save with transaction
await _appContext.TransactWriter.SaveChangesAsync(cancellationToken);

Update

appContext.Users.Add(newUser);
await _appContext.BatchWriter.SaveChangesAsync(cancellationToken);

newUser.Email = "example@example.com";

// Update with batch
await _appContext.BatchWriter.SaveChangesAsync(cancellationToken);
// Update with transaction
await _appContext.TransactWriter.SaveChangesAsync(cancellationToken);

Remove

// Find user by id...

_appContext.Users.Remove(newUser);
        
// Save with batch 
await _appContext.BatchWriter.SaveChangesAsync(cancellationToken);
// Save with transaction
await _appContext.TransactWriter.SaveChangesAsync(cancellationToken);

How to query

Query by partition key

var user = await _appContext.Query<User>()
    .PartitionKey($"USER#{id}")
    .ToEntityAsync(cancellationToken);

Query by partition key and sort key

 var item = await _appContext.Query<Item>()
    .PartitionKey($"ORDER#{orderId}")
    .SortKey(QueryOperator.Equal, $"ITEM#{itemId}")
    .ToEntityAsync(cancellationToken);

Query by partition key using global secondary index

var user = await _appContext.Query<User>()
    .IndexName("GSI1PK-GSI1SK-index")
    .PartitionKey("GSI1PK", $"USER#{id}")
    .ToEntityAsync(cancellationToken);

Query by partition key and sort key using global secondary index

var item = await _appContext.Query<Item>()
    .IndexName("GSI1PK-GSI1SK-index")
    .PartitionKey("GSI1PK", $"ORDER#{orderId}")
    .SortKey("GSI1SK", QueryOperator.BeginsWith, "ITEM#")
    .ToListAsync(cancellationToken);

Query with filter

var item = await _appContext.Query<Item>()
    .IndexName("GSI1PK-GSI1SK-index")
    .PartitionKey("GSI1PK", $"ORDER#{orderId}")
    .SortKey("GSI1SK", QueryOperator.BeginsWith, "ITEM#")
    .Filter(item => item.Units >= 5 && item.Units < 10)
    .ToListAsync(cancellationToken);

Query with Limit, ConsistentRead, ScanIndexForward and AsNoTracking

var items = await _appContext.Query<Item>()
    .IndexName("GSI1PK-GSI1SK-index")
    .PartitionKey("GSI1PK", $"ORDER#{orderId}")
    .SortKey("GSI1SK", QueryOperator.BeginsWith, "ITEM#")
    .Limit(limit)
    .ConsistentRead()
    .ScanIndexForward()
    .AsNoTracking()
    .ToListAsync(cancellationToken);

Version control (Optimistic locking)

Add config
// 'v' represents the version attribute for optimistic locking
builder.Services.AddDynamoSharpContext<EcommerceContext>("dynamosharp", "v");
Context
public class EcommerceContext : DynamoSharpContext
{
    ...

    public override void OnModelCreating(IModelBuilder modelBuilder)
    {
        ...

        // Enable optimistic locking for Order entity
        modelBuilder.Entity<Order>()
            .HasVersioning(); 

        // Enable optimistic locking for Item entity
        modelBuilder.Entity<Item>()
            .HasVersioning();
    }
}

Thread Safety Considerations

DynamoSharp does not support concurrent operations on the same DynamoSharpContext instance. Avoid running multiple asynchronous queries in parallel or accessing the context from multiple threads simultaneously. Always await async calls immediately or use separate DynamoSharpContext instances for parallel operations.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.2 121 8/13/2025
0.1.1 181 8/5/2025
0.1.0 186 8/5/2025