Reyan Chougle
Reyan Chougle

Reputation: 5321

Entity Framework Core - Add and Update same entity with single SaveChangesAsync()

I have a table like this:

[Id] [INT] IDENTITY(1,1) NOT NULL,
..
..
[ParentId] [INT] NULL,
[CreatedOn] [DATETIME] NOT NULL,
[UpdatedOn] [DATETIME] NOT NULL

In some case I want to update the ParentId with the Id of the table like this:

_dbContext.Add(data);

if (true)
{
   data.ParentId = data.Id;    
}

_dbContext.Update(data); 

await _dbContext.SaveChangesAsync();

When doing so, I am getting this error:

The property 'Id' on entity type 'Data' has a temporary value while attempting to change the entity's state to 'Modified'. Either set a permanent value explicitly or ensure that the database is configured to generate values for this property.

Is it possible what I am trying to do or I need to first call the SaveChangesAsync() before update?

Upvotes: 0

Views: 4325

Answers (1)

Flater
Flater

Reputation: 13783

The error you're getting

Entity Framework has something called a change tracker. This performs a few duties, I'll mention the ones relevant for this answer:

  • It keeps track of what needs to happen when you call SaveChanges()
  • It keeps tabs on all attached entities

When you call SaveChanges(), EF might need to INSERT some entities, and it needs to UPDATE some others (I'm ignoring other operations as they are irrelevant here. To keep track of this, EF's change tracker attached a particular enum called EntityState to your entity. Based on the method you call, the enum value gets set.

_dbContext.Add(data);

var the_enum_value = _dbContext.Entry(data).State;

You will see that the_enum_value equals EntityState.Added. This means that when you call SaveChanges(), an INSERT statement will be generated.

_dbContext.Update(data); //let's assume this is an existing entity you fetched

var the_enum_value = _dbContext.Entry(data).State;

Here, you will see that the_enum_value equals EntityState.Modified. This means that when you call SaveChanges(), an UPDATE statement will be generated.

You added a new entity, so you clearly want to INSERT it to the database. But by calling Update on this entity, you would change the enum value to EntityState.Modified, therefore causing an UPDATE statement to be generated, even though there is no existing row in the database yet that you need to update.

In the past, EF just changed the enum, tried anyway, and then you'd be scratching your head as to why your changes weren't making it to the database.

Nowadays, EF is smart enough to realize that if the entity doesn't have an actual ID value yet, that setting the enum to EntityState.Modified is going to be a bad idea, so it alerts you that you're trying to do something that won't work.

The general solution here is that you simply didn't need to call Update. You could do this:

var data = new Data() { Name = "Foo" };

_dbContext.Add(data);

data.Name = "Bar";

await _dbContext.SaveChangesAsync();

You will see that the name hitting the database is "Bar", not "Foo". Because EF only generates the INSERT statement when you call SaveChanges, not when you call Add; therefore any changes made before calling SaveChanges are still "seen" by the INSERT statement being generated.


What you're attempting

HOWEVER, you're in a particularly special case because you're trying to access and use the ID property of the to-be-created entity. That value does not yet exist.

EF does its best to ignore this and make it work for you behind the scenes. When you call SaveChanges(), EF will find out what the generated ID is and will silently fill it in for you so you can keep using your data variable without needing to worry.

But I very much doubt that EF is going to be able to realize that data.ParentId needs the same treatment.

Do I need to first call the SaveChangesAsync() before update?

In this very specific case, most likely yes. However, this is because you're trying to use the data.Id value, and this is unrelated to the error message you reported.

Upvotes: 1

Related Questions