EF Core 5 - Updating an entity in many-to-many relation throws "Violation of PRIMARY KEY" error

-1

I am working with .NET 5 and using EF Core and MSSQL for database.

I have 2 classes that have many to many relationship between them. Those were migrated from models to DB. example of DB tables

Migration:

migrationBuilder.CreateTable(
            name: "TournamentUser",
            columns: table => new
            {
                TournamentsId = table.Column<int>(type: "int", nullable: false),
                UsersId = table.Column<int>(type: "int", nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_TournamentUser", x => new { x.TournamentsId, x.UsersId });
                table.ForeignKey(
                    name: "FK_TournamentUser_Tournaments_TournamentsId",
                    column: x => x.TournamentsId,
                    principalTable: "Tournaments",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
                table.ForeignKey(
                    name: "FK_TournamentUser_Users_UsersId",
                    column: x => x.UsersId,
                    principalTable: "Users",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });

Models:

public class Tournament
{
    [Key]
    public int Id { get; set; }
    [Required, MaxLength(20)]
    public string Name { get; set; }
    [Required]
    public int MaxPlayers { get; set; }
    public int PlayersCount { get; set; }
    [Required]
    public DateTime Start { get; set; }
    public bool IsStarted { get; set; }
    public bool IsEnded { get; set; }
    public List<User> Users { get; set; }
}

public class User
{
    [Key]
    public int Id { get; set; }
    [Required, MaxLength(15)]
    public string Username { get; set; }
    [Required]
    public string Password { get; set; }
    [Required, MaxLength(20)]
    public string Name { get; set; }
    [Required, MaxLength(20)]
    public string Surname { get; set; }
    [JsonIgnore]
    public List<Tournament> Tournaments { get; set; }
}

So as I try to update Tournaments model

[HttpPut]
public async Task<ActionResult<Tournament>> Put([FromBody] Tournament tournament)
{
    _context.Tournaments.Update(tournament);
    await _context.SaveChangesAsync();

    return CreatedAtAction(nameof(GetTournament), new { id = tournament.Id }, tournament);
}

I get this error

Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_TournamentUser'. Cannot insert duplicate key in object 'dbo.TournamentUser'. The duplicate key value is (1, 1).

What could be possible solutions for this? I would like to keep my many to many table udpated as Tournament's User can be removed or added each time I update Tournament.

EDIT:
Following shows the tournament object data -

Example of updatable tournament object

c#
sql-server
asp.net-core
entity-framework-core
many-to-many
asked on Stack Overflow Mar 14, 2021 by KÄ™stutis Ramulionis • edited Mar 15, 2021 by atiyar

3 Answers

1

The Solution:

To update the Tournament entity only use the following approach -

_context.Entry(tournament).State = EntityState.Modified;
await _context.SaveChangesAsync();

The Explanation:

  1. The Update() method marks the entity as Modified if it has an Id (auto generated primary key) value, and as Added if it doesn't have an Id value. Same is true for any related entities if you call the method with an entity graph (entity with related entities). Then on the next SaveChanges() call, EF generates update and/or insert command for the entity (or entities) based on whether they are marked as Modified or Added. The point is - the Update() method does not only update an entity, it can insert new entities too.

  2. In your case, looking at the models, EF can tell Tournament and User are in a many-to-many relationship, and there is a joining entity TournamentUser at database level. But since you are receiving the entity graph from outside, they are not tracked by EF and it cannot tell whether the TournamentUser entities for this tournament and the related users already exist in database or not.

  3. As you are calling the Update() method with the entity graph, the tournament, the related users, and the joining entity relating them (TournamentUser) all becomes subject for update operation. And EF tries to create the joining entity just like it would create/insert a new entity without an Id value.

  4. EF generates insert command for a TournamentUser entity and update commands for Tournament and User entities. But at database level, since the TournamentUser link already exists you get the Violation of PRIMARY KEY error.

The -

_context.Entry(tournament).State = EntityState.Modified;

suggested above explicitly marks only the tournament as Modified. Therefore, EF doesn't bother to look at any related entities, or to infer any relationship, and only an update command is generated for the tournament.

answered on Stack Overflow Mar 14, 2021 by atiyar • edited Mar 17, 2021 by atiyar
0

Try to remove users from your torment object if you want to update tournment.

 public async Task<ActionResult<Tournament>> Put([FromBody] Tournament tournament)
    {
         tournament.Users=null;
        _context.Tournaments.Update(tournament);
        await _context.SaveChangesAsync();
.....
   
    }

Or if you want to add an user to your tournament

var existing= _context
              .Tournaments
              .Include(u=>u.Users)
              .FirstOrDefault(t=> t.Id== tournament.Id);

if(existing==null) return ...error

if( existing.Users==null) existing.User=new List<User>();

if( existing.Users.Any(u=>u.Id=tournment.User.Id) return ...error

existing.Users.Add(tournment.User);
_context.Entry(existing).State = EntityState.Modified;
_context.SaveChanges();

It looks ackward but you are paying for not adding many-to-many relantionship table explicitly. If you use UserTournment table code will be shorter.

You can try this. I never tried it. Let me know if it works:

var existing = _context
              .Tournaments
              .Include(u=>u.Users)
              .FirstOrDefault(t=> t.Id== tournament.Id);

if(existing==null) return ...error

_context.Entry(existing).CurrentValues.SetValues(tournment);

_context.SaveChanges();
answered on Stack Overflow Mar 14, 2021 by Serge • edited Mar 14, 2021 by Serge
0

I have found a solution from this post

I have modified the code a bit, since originally it was removing users from table and I got the result I wanted

public async Task<ActionResult<Tournament>> Put([FromBody] Tournament tournament)
    {
        var _tournament = _context.Tournaments.Include(t => t.Users).FirstOrDefault(t => t.Id == tournament.Id);
        _context.Entry(_tournament).CurrentValues.SetValues(tournament);
        var _users = _tournament.Users.ToList();
        // Adds new Users
        foreach (var tournamentUser in tournament.Users)
        {
            if (_users.All(i => i.Id != tournamentUser.Id))
            {
                _tournament.Users.Add(tournamentUser);
            }
        }
        // Removes old Users
        foreach (var tournamentUser in _users)
        {
            if (tournament.Users.FirstOrDefault(tu => tu.Id == tournamentUser.Id) == null)
            {
                _tournament.Users.Remove(tournamentUser);
            }
        }
        await _context.SaveChangesAsync();

        return CreatedAtAction(nameof(GetTournament), new { id = tournament.Id }, tournament);
    }

Not sure if it's not over complex but it works


User contributions licensed under CC BY-SA 3.0