Error: "alternate key property 'id' is null" with "Update" call to Cosmos DB and Entity Framework Core 3.1.2

Viewed 1588

Let's say I have a simple model of PersonModel as follows which I update the FirstName property from an ASP.NET Core 3.1 Blazor or MVC UI (web form):

public class PersonModel 
{
  public Guid PersonModelId { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
}

I send the model to an http triggered Azure Function and bind to the model. This is where I implement EF Core and perform the following to update it:

public async Task<int> UpdateAsync(TEntity entity)
{
  _dbSet.Attach(entity); //<--this throws the error
  _dbContext.Entry(entity).State = EntityState.Modified;
  return await _dbContext.SaveChangesAsync();
}

I have no issue creating or deleting entities with this model and given the following article, I don't see any issue creating a model which is 'clean' from any specific repository requirements (i.e. like a partition key or primary key, etc.) however I can't seem to run the update code without getting the following error:

Unable to track an entity of type 'PersonModel' because alternate key property 'id' is null.

Is anyone aware of specific requirements around the model and using this method to update an entity? Is there an alternative which is as easy/elegant as the example from the docs article?

I've tried _dbContext.Update(entity) but I get the exact same error which leads me to believe my model needs an "ID" property.

I've also tried modifying my DbContext setup as follows:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.Entity<PersonModel>().HasAlternateKey(p => p.PersonModelId);
}

...but this just gives me the same error and also doesn't change the way the entity is stored.

Update

Following the guidance from the accepted answer below, I wrote the following changes to make this work:

public async Task<int> UpdateAsync(TEntity entity)
{
  var entry = _dbSet.Add(entity);
  entry.State = EntityState.Unchanged;
  _dbContext.Update(entity);
  return await _dbContext.SaveChangesAsync();
}
2 Answers

Edit: I believe the issue arises from a property that EF Core automatically generates for each item named "id", specifically all lowercased and separate from your key(s) defined in your model. Appears to be specific to the Cosmos DB provider and the null value issue is noted within the documentation for EF Core -> Database Providers -> Cosmos under the section "Working with Disconnected Entities".

Apparently EF Core only generates this property when entity enters the "Added" state. So when you call Attach, this "id" property is null hence the exception. A workaround is listed in the documentation with a much better explanation than I can give.

Linked below to EF Core docs page for Cosmos provider. Scroll to bottom of overview page to find the disconnected entities section that covers this.

https://docs.microsoft.com/en-us/ef/core/providers/cosmos/?tabs=dotnet-core-cli#working-with-disconnected-entities

According to this issue [1] you'll need to get the entry and set the item id explicitly:

public async Task<T> UpdateItemAsync(string id, T item)
    {
        var itemEntry = context.Entry(item);            
        itemEntry.Property<string>("id").CurrentValue = "Item|" + id;
        itemEntry.State = EntityState.Modified;            
        await context.SaveChangesAsync();
        return itemEntry.Entity;

    }

[1] https://github.com/dotnet/efcore/issues/15289

Related