Search code examples
c#asp.net-coreentity-framework-coreterraformazure-cosmosdb

CosmosDB PartitionKey Error Despite Matching Partition Key Setup in EF Core and Terraform


I'm working with CosmosDB in an ASP.NET Core application using EF Core. I have a Post class with an Slug property that I use as the partition key. However, I'm encountering an error when trying to save a Post object to CosmosDB.

Here is the Post class:

public partial class Post 
{
    public string Id { get; set; }

    [Key]
    public string Slug { get; set; }

    [Required]
    [MaxLength(100)]
    public string Title { get; set; }

    [Required]
    [MaxLength(100)]
    public string Subtitle { get; set; }

    public string Markdown { get; set; }

    [Required]
    [MaxLength(50)]
    public string Author { get; set; }

    [DataMember(Name = "created_at")]
    [JsonPropertyName("created_at")]
    public DateTime? CreatedAt { get; set; }

    [DataMember(Name = "updated_at")]
    [JsonPropertyName("updated_at")]
    public DateTime? UpdatedAt { get; set; }
}

I generate the Id using Guid.NewGuid().ToString() and then save the object to CosmosDB:

public async Task<Post> CreatePostAsync(PostInput postInput)
{
    PostInputValidator.CheckPostInputRequiredFields(postInput);

    Post post = new()
    {
        Id = Guid.NewGuid().ToString(),
        Slug = Guid.NewGuid().ToString() + "-" + postInput.Title.ToLower().Replace(" ", "-") + "-" +   postInput.Author.ToLower(),
        Title = postInput.Title,
        Subtitle = postInput.Subtitle,
        Author = postInput.Author,
        Markdown = postInput.Markdown,
        CreatedAt = DateTime.UtcNow
    };

    _context.Posts.Add(post);

    await _context.SaveChangesAsync();

    return post;
}

However, I get the following error:

PartitionKey extracted from document doesn't match the one specified in the header. Learn more: https://aka.ms/CosmosDB/sql/errors/wrong-pk-value

Here's my DbContext:

public class BlogContext : DbContext, IBlogContext
{
    public BlogContext(DbContextOptions<BlogContext> options)
        : base(options)
    {
    }

    public DbSet<Post> Posts { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Post>().ToContainer("Posts"); 
        modelBuilder.Entity<Post>().HasPartitionKey(p => p.Slug);
        modelBuilder.Entity<Post>().HasNoDiscriminator();
    }
}

Here's my Terraform CosmosDB setup:

resource "azurerm_cosmosdb_account" "gt_cosmosdb" {
  name                = "gt-cosmosdb"
  location            = azurerm_resource_group.gt_rg.location
  resource_group_name = azurerm_resource_group.gt_rg.name
  offer_type          = "Standard"
  kind                = "GlobalDocumentDB"
  enable_free_tier    = true
  consistency_policy {
    consistency_level = "Session"
  }
  geo_location {
    location          = azurerm_resource_group.gt_rg.location
    failover_priority = 0
  }
}

resource "azurerm_cosmosdb_sql_database" "gt_database" {
  name                = "gt-db"
  resource_group_name = azurerm_cosmosdb_account.gt_cosmosdb.resource_group_name
  account_name        = azurerm_cosmosdb_account.gt_cosmosdb.name
}

resource "azurerm_cosmosdb_sql_container" "gt_container" {
  name                = "Posts"
  resource_group_name = azurerm_cosmosdb_account.gt_cosmosdb.resource_group_name
  account_name        = azurerm_cosmosdb_account.gt_cosmosdb.name
  database_name       = azurerm_cosmosdb_sql_database.gt_database.name
  partition_key_path  = "/slug"
  throughput          = 400
}

Things I've tried:

  1. Setting the partition key to Id in EF Core and CosmosDb
  2. Generating the Id as a GUID and using .ToString()
  3. Changing the type of the partition key from string to int and back
  4. Changing back to /slug partition key

Despite this, the error persists. It appears the partition key isn't matching, even though I've configured it to use the Slug in both EF Core and my Terraform configuration.

Questions:

  1. What could be causing this mismatch between the partition key specified in the document and the one in the header?
  2. Is there something wrong with the way I'm generating or setting the partition key?
  3. Could there be an issue with how the partition key is configured in Terraform?

Any help or guidance would be greatly appreciated!


Solution

  • This is just a shot in the dark, but I believe the partition key is case-sensitive, and currently the casing between your actual partition key property is different from what you've specified in the Terraform.

    Try either changing the Terraform to:

    partition_key_path  = "/Slug"
    

    Or change the JSON name of your Slug property:

    [Key]
    [JsonProperty("slug")]
    public string Slug { get; set; }
    

    Whichever works best for you.