Skip to content

Add EnsureCreated support for DynamoDB tables and indexes #202

Description

@j-d-ha

Summary

Add DynamoDB EnsureCreated / schema creation support following EF Core Cosmos and relational provider patterns.

Current provider can translate/write items, but DynamoDB Local/test containers require physical tables to exist before seed/save writes can run. This blocks specification tests and any TestStore.InitializeAsync flow that assumes provider EnsureCreated can provision backing store resources.

Desired provider behavior

Follow Cosmos/relational pattern:

  • SaveChanges must not auto-create tables.
  • Calling DbContext.Database.EnsureCreated() / EnsureCreatedAsync() is explicit opt-in to create missing backing resources.
  • If caller does not call EnsureCreated, tables are expected to be provisioned externally.
  • MVP can be model-driven with provider defaults; no per-call options required initially.

Relational precedent:

  • EnsureCreated() creates database/tables from model.
  • Opt-out is: do not call EnsureCreated; use migrations/external schema management.

Cosmos precedent:

  • EnsureCreatedAsync() creates database and containers via CreateDatabaseIfNotExistsAsync / CreateContainerIfNotExistsAsync.
  • Container definitions are derived from EF model annotations: container name, partition key, TTL, throughput, indexes, etc.
  • No per-call options struct; behavior is model-driven.

Mongo precedent for later consideration:

  • Mongo adds provider-specific MongoDatabaseCreationOptions to control missing collections/indexes.
  • We can defer analogous Dynamo options until needed.

Scope

Implement DynamoDB database/table creation support sufficient for local/test-container and normal EnsureCreated usage.

Required

  • Register DynamoDB database creator service:
    • IDatabaseCreator implementation, e.g. DynamoDatabaseCreator.
    • Support sync and async EF APIs where practical:
      • EnsureCreated() / EnsureCreatedAsync()
      • EnsureDeleted() / EnsureDeletedAsync()
      • CanConnect() / CanConnectAsync() if feasible.
  • Derive physical table definitions from existing provider metadata.
  • Create missing DynamoDB tables before model seed/save writes run.
  • Wait for table active after creation.
  • Delete mapped tables for EnsureDeleted.
  • Integrate with EF model seed/UseSeeding paths if applicable, matching Cosmos behavior.

Table creation

For each table group:

  • Table name from centralized table-group metadata:
    • src/EntityFrameworkCore.DynamoDb/Extensions/DynamoModelExtensions.cs
    • GetTableGroupName() / ComputeTableGroupName()
  • Key source from inheritance-aware key metadata:
    • ResolveKeyMappedEntityType()
  • Base table key:
    • partition key property -> DynamoDB HASH key
    • optional sort key property -> DynamoDB RANGE key
  • Attribute names from GetAttributeName().
  • Attribute scalar types from effective provider CLR/key type logic:
    • string -> ScalarAttributeType.S
    • numeric -> ScalarAttributeType.N
    • byte[] -> ScalarAttributeType.B

Index creation

Create configured secondary indexes from existing metadata:

  • Runtime index model:
    • src/EntityFrameworkCore.DynamoDb/Metadata/Internal/DynamoRuntimeTableModel.cs
    • DynamoRuntimeTableModel
    • DynamoTableDescriptor
    • DynamoIndexDescriptor
  • Runtime initializer that already builds index descriptors:
    • src/EntityFrameworkCore.DynamoDb/Infrastructure/Internal/DynamoModelRuntimeInitializer.cs
    • BuildRuntimeTableModel
    • BuildSourceDescriptors
    • BuildGlobalSecondaryIndexDescriptor
    • BuildLocalSecondaryIndexDescriptor
  • Index annotations/extensions:
    • src/EntityFrameworkCore.DynamoDb/Extensions/DynamoIndexExtensions.cs
    • DynamoSecondaryIndexKind.Global
    • DynamoSecondaryIndexKind.Local
    • DynamoSecondaryIndexProjectionType

Need map descriptors to AWS SDK table create inputs:

  • GlobalSecondaryIndexes
  • LocalSecondaryIndexes
  • KeySchema
  • AttributeDefinitions
  • projection type

Deduplicate AttributeDefinitions across table and indexes.

Billing/defaults

MVP default:

  • Use BillingMode.PAY_PER_REQUEST so no throughput config is required and DynamoDB Local/test containers work out of box.

Later enhancement:

  • Add provider model annotations/options for billing mode and provisioned throughput if needed.
  • Possible provider-specific API similar to Mongo:
    • DynamoDatabaseCreationOptions(CreateMissingTables, CreateSecondaryIndexes, BillingMode, ...)

Important current code references

Provider registration likely belongs near:

  • src/EntityFrameworkCore.DynamoDb/Extensions/DynamoServiceCollectionExtensions.cs (or equivalent service registration file)

Write paths currently assume tables exist:

  • src/EntityFrameworkCore.DynamoDb/Storage/DynamoSaveChangesPlanner.cs
  • src/EntityFrameworkCore.DynamoDb/Storage/DynamoPartiqlStatementFactory.cs
  • src/EntityFrameworkCore.DynamoDb/Storage/DynamoWriteExecutor.cs
  • src/EntityFrameworkCore.DynamoDb/Storage/DynamoClientWrapper.cs

Runtime table/index metadata already exists and should be reused:

  • src/EntityFrameworkCore.DynamoDb/Metadata/Internal/DynamoRuntimeTableModel.cs
  • src/EntityFrameworkCore.DynamoDb/Infrastructure/Internal/DynamoModelRuntimeInitializer.cs

Table/key helpers recently centralized:

  • src/EntityFrameworkCore.DynamoDb/Extensions/DynamoModelExtensions.cs
    • GetTableGroupName()
    • ComputeTableGroupName()
    • ResolveTableMappedEntityType()
    • ResolveKeyMappedEntityType()

Test infra currently only ensures client exists, not tables:

  • tests/EntityFrameworkCore.DynamoDb.SpecificationTests/TestUtilities/DynamoTestStore.cs

Acceptance criteria

  • context.Database.EnsureCreated() creates all required DynamoDB tables for mapped root/table groups.
  • context.Database.EnsureCreatedAsync() async equivalent works.
  • DynamoDB Local/test container seed writes no longer fail due to missing table.
  • Tables use correct PK/SK attribute names and DynamoDB scalar types.
  • Configured GSIs and LSIs are created.
  • Shared-table/entity inheritance mappings create one physical table per table group.
  • Calling EnsureCreated repeatedly is idempotent.
  • EnsureDeleted removes mapped tables and ignores missing tables.
  • Unit/integration tests cover:
    • PK-only table
    • PK+SK table
    • TPH/shared table inheritance
    • GSI
    • LSI
    • repeated EnsureCreated
    • seed data after creation

Notes

This work should land before continuing current spec-test branch work, because tests using DynamoDB Local need actual table definitions. Provider should not add table creation to SaveChanges; explicit EnsureCreated is EF-compatible opt-in.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions