0

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?

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the great explanation, My concern is I do not want to make two database calls. So, trying to make it one database commit here
@ReyanChougle: As the error mentions, then you're going to have to manually set the ID, but that brings with it its own challenges (conflicts, race conditions, etc). You're going to be much better off simply performing a second really simple and straightforward call here.
@ReyanChougle: A year and a half later, I've been working on a project where the IDs are being set manually for a very similar reason to yours, and I have to say that there's not as many challenges as I expected there to be. We are using GUIDs however to make sure that any ID we generate is reliably unique.
Good to hear from you after so long. Is your PK/FK not an integer?
@ReyanChougle: I tend to favor GUID in general because it makes it easy to not have to worry about conflicts. But there are reasons to not use a GUID (it's not the most performant data type to put an index on, I am told), so don't take my opinion as a rule :) By default, I would say that if you generate your own PK values, it makes life easier to use GUID simply because you can freely generate values without worrying about their uniqueness. That being said, you could just use an integer and then manually check what the current max ID is and then do +1, but be careful with race conditions then.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.