Nikita Fedorov
Nikita Fedorov

Reputation: 870

Entity Framework Core. How to update the related data properly?

This question is a common one, but I still can't understand how to update the related entity properly?

I have the following code:

    public async Task<bool> UpdateStepAssignedToLevelAsync(Step step, Guid levelId, int priority = -1)
    {
        var item = await this._context.StepLevels
            .Include(sl => sl.Step)
            .FirstOrDefaultAsync(x => x.StepId == step.Id && x.LevelId == levelId);
        if (item == null)
        {
            return false;
        }

        //this._context.Entry(item).State = EntityState.Detached;
        if (priority > -1)
        {
            item.Priority = priority;
        }

        item.Step = step;

        //this._context.StepLevels.Update(item);
        var rows = await this._context.SaveChangesAsync();
        return rows > 0;
    }

When it runs I'm getting the following error:

InvalidOperationException: The instance of entity type 'Step' cannot be tracked because another instance with the key value '{Id: 35290c18-5b0a-46a5-8f59-8888cf548df5}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

As I understand the entity is being tracked since the select request at the method's start. Okay, but when I'm detaching the entity and calling the Update method (see commented lines), the Step entity is not being changed. But StepLevel does: the priority is changing. When I'm trying to call just Update the EF tries to insert the new Step instead of updating the existing.

So, could you please advice me, what is the best practicing here?

Thanks a lot in advance!

Upvotes: 3

Views: 9791

Answers (2)

Mofaggol Hoshen
Mofaggol Hoshen

Reputation: 738

The context is tracking the corresponding Step entity loaded from the DB for that reason it is throwing this error. You can update each Step property -

public bool UpdateStepAssignedToLevelAsync(Step step, int levelId)
        {
            using(var context = new StackOverFlowDbContext())
            {
                var item = context.StepLevels
                                  .Include(sl => sl.Step)
                                  .FirstOrDefault(x => x.Id == step.Id && x.Id == levelId);
                if (item == null)
                {
                    return false;
                }

                // Updating Name property
                item.Step.Name = step.Name;

                // Other properties can be upadated

                var rows = context.SaveChanges();
                return rows > 0;
            }

        }

Upvotes: 0

Ivan Stoev
Ivan Stoev

Reputation: 205539

First, detaching an entity does not detach related entities, so detaching item does not detach the item.Step retrieved with .Include(sl => sl.Step).

Second, since you don't want to change the item.Step, but to update the existing Step entity (x.StepId == step.Id), and you know that the context is tracking (contains) the corresponding Step entity loaded from the database, you should use the procedure for updating a db entity from a detached entity via Entry(...).CurrentValues.SetValues method.

So remove

item.Step = step;

and use the following instead:

this._context.Entry(item.Step).CurrentValues.SetValues(step);

Last note. When you work with attached entities, there is no need to (should not) use Update method. The change tracker will automatically detect the changed property values if any of the entities being tracked.

Upvotes: 9

Related Questions