Another instance of an entity raises an error when update

Enrico Rossini 201 Reputation points
2025-02-19T21:40:19.15+00:00

In my ASP.NET Core 9 Web API and Entity Framework Core, I have a model class for a Client that has a relationship many to many with the model class Channel.

public class Client
{
    [Key]
    public long ID { get; set; }
}

public class Channel
{
    [Key]
    public long ID { get; set; }
    public string? Name { get; set; }
    public ICollection<Client>? Client { get; set; }
}

After the migration, the database has the structure of a new table as I expect:

enter image description here

I can add new values using Entity Framework Core. The problem starts when I want to update the values.

I have an API, with a PUT verb to be precise, that receives the Client object as a parameter with all the details. First I read the object from the database including the Channels:

var localClient = await db.Client.AsNoTracking()
                      .Include(c => c.Channels)
                      .FirstOrDefaultAsync(model => model.ID == id);

Then, I map the parameter with the data from the database:

localClient = mapper. Map<Domain.Client>(client);

And then I update the Channels using the values from the parameter:

localClient.Channels?.Clear();

if (client.Channels != null)
{
    var listChannels = client.Channels.ToList();

    foreach (Channel ch in listChannels)
    {
        var l = await db.Channels.Where(c => c.ID == ch.ID).FirstOrDefaultAsync();

        if (l != null)
            if (localClient.Channels!.Count(c => c.ID == l.ID) == 0)
                localClient.Channels?.Add(l);
    }
}

If I inspect the localClient object, there are only unique channels and no duplication. When I want to save using

db.Attach(client);

I immediately get this error:

The instance of entity type 'Channel' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.

I can't understand why I get this error. I checked my old projects and I use a similar process.

Update

client is an object that I pass via API: this contains all the details about the client, also the list of channels.

group.MapPut("/{id}",
    async Task<Results<Ok, NotFound>> (long id, Domain.Client client, 
    MyDbContext db, IMapper mapper) =>
    {
        // code above
    }

I fetch the client from the database because I was thinking that the error was related to a new instance or record instead of updating an existing one.

Update/2

I created on GitHub a small project to test the update. I applied the suggestions below but the object is not updated on the database.

C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,305 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Michael Taylor 56,956 Reputation points
    2025-02-19T22:27:12.3466667+00:00

    It's failing because you're trying to add a new row to the table with the same ID as an existing row. It also sort of looks like you have a unique constraint on the DB table such that each client can be associated with the same channel once.

    Where things are going wrong is in the fact that you're not actually tracking the object in the DB, you're creating a new one. You actually do this twice in the original post. If you intend to update data in the DB using EF then you should never call AsNoTracking on the object when you get it. The net effect of this call is that EF doesn't track changes to the object. Later you assign a new value to the client which ensures that it is not the original object EF is tracking.

    Looking at your Github sample things are more complex.

    1. You're still doing a AsNoTracking which you shouldn't do for an update.
    2. When it comes time to add channels you're fetching the channels from the DB and adding them to the list of channels.

    Try this in your MapPut.

    var localClient = await db.Clients.Include(x => x.Channels).FirstOrDefaultAsync(x => x.Id == id);
    
    //Remove the old ones
    localClient.Channels.RemoveAll(x => channelIdsToRemove.Contains(x.Id));
    
    //Add the new ones, note that I assume you've already handled looking for dups
    //Don't need to actually fetch the channels from the DB first since EF should
    //be able to figure out that the channel already exists in the DB based upon its unique ID
    localCLient.Channels.AddRange(channelIdsToAdd.Select(x => new Channel() { Id = x }));
    
    await db.SaveChangesAsync();
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.