【问题标题】:EF Core 5 - Updating an entity in many-to-many relation throws "Violation of PRIMARY KEY" errorEF Core 5 - 在多对多关系中更新实体会引发“违反主键”错误
【发布时间】:2021-06-11 23:54:42
【问题描述】:

我正在使用.NET 5 并使用EF CoreMSSQL 作为数据库。

我有 2 个类,它们之间有多对多的关系。那些从模型迁移到数据库。 example of DB tables

迁移:

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);
            });

型号:

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; }
}

所以当我尝试更新锦标赛模型时

[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);
}

我收到此错误

Microsoft.Data.SqlClient.SqlException (0x80131904):违反 主键约束“PK_TournamentUser”。不能插入重复 键入对象“dbo.TournamentUser”。重复键值为 (1, 1)。

对此有什么可能的解决方案?我想保持我的多对多表更新,因为每次更新 Tournament 时都可以删除或添加 Tournament's User

编辑:
以下显示了锦标赛对象数据 -

【问题讨论】:

  • 您能否展示您正在尝试更新的 Tournament 锦标赛对象,好吗?是否包括用户?
  • 我将它附在帖子的末尾。它确实包括已插入到先前更新操作中的用户。不知何故,更新锦标赛强制插入所有用户锦标赛关系,即使它已经有一个。

标签: c# sql-server asp.net-core entity-framework-core many-to-many


【解决方案1】:

解决方案:

要更新Tournament 实体使用以下方法 -

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

解释:

  1. Update() 方法将实体标记为 Modified,如果它具有 Id(自动生成的主键)值,则标记为 Added,如果它没有 Id 值。如果您使用实体图(具有相关实体的实体)调用该方法,则任何相关实体也是如此。然后在下一次SaveChanges() 调用中,EF 根据实体(或多个实体)是否标记为ModifiedAdded 生成更新和/或插入命令。重点是 - Update() 方法不仅可以更新实体,还可以插入新实体

  2. 在您的情况下,查看模型,EF 可以判断 TournamentUser 处于多对多关系,并且在数据库级别有一个连接实体 TournamentUser。但由于您是从外部接收实体图,EF 不会对其进行跟踪,并且无法判断本次锦标赛的 TournamentUser 实体和相关用户是否已存在于数据库中。

  3. 当您使用实体图调用 Update() 方法时,锦标赛、相关用户以及与他们相关的加入实体 (TournamentUser) 都将成为更新操作的主题。并且 EF 尝试创建加入实体,就像它创建/插入一个没有 Id 值的新实体一样。

  4. EF 为TournamentUser 实体生成插入命令,并为TournamentUser 实体生成更新命令。但在数据库级别,由于TournamentUser 链接已经存在,您会收到Violation of PRIMARY KEY 错误。

-

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

上面的建议明确地将锦标赛标记为Modified。因此,EF 不会费心去查看任何相关实体,或推断任何关系,而只会为锦标赛生成更新命令。

【讨论】:

  • 我已经想出了如何只更新锦标赛,但我的主要目标是更新锦标赛和嵌套实体。正如我提供的数据库样本一样,虽然我有 2 个模型,但有 3 个表,我想保持这种状态,希望实体框架能够管理第 3 个表。这可能吗?
  • 感谢您的帮助,我设法找到了解决方案,将其发布在下面。
  • 感谢 atiyar 的解决方案和非常详细的解释。这个解决方案是我想要的,我只想像过去一样更新实体本身,只为实体提供简单的 json 数据
【解决方案2】:

我从this post找到了解决方案

我已经稍微修改了代码,因为最初它是从表中删除用户并且我得到了我想要的结果

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);
    }

不确定它是否过于复杂,但它确实有效

【讨论】:

  • 您的帖子没有提到在相关列表中添加/删除用户(即更新用户列表本身,而不是该列表中的实体)。您应该在帖子中说明您要达到的目标,您期望的结果是什么。
  • 我怎么没提?帖子中显然说明了:“有什么可能的解决方案?我想保持我的多对多表更新,因为每次更新锦标赛时都可以删除或添加锦标赛的用户。”
  • 这可能会更加简化代表您的问题。哈哈。或者,可能是我的错误,我没能理解你试图表达的意思。
【解决方案3】:

如果您想更新锦标赛,请尝试从您的折磨对象中删除用户。

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

或者,如果您想将用户添加到您的锦标赛中

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();

它看起来很糟糕,但是您要为没有明确添加多对多关系表而付出代价。如果使用 UserTournment 表代码会更短。

你可以试试这个。我从未尝试过。让我知道它是否有效:

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();

【讨论】:

  • 但是它不会将锦标赛用户关系添加到数据库中
  • 这绝对不是我要的东西
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-30
  • 1970-01-01
  • 2021-06-27
相关资源
最近更新 更多