尝试插入具有 1:N 关系的实体时,重复键值违反 EntityFramework 中的唯一约束“PK_Users”错误

Posted

技术标签:

【中文标题】尝试插入具有 1:N 关系的实体时,重复键值违反 EntityFramework 中的唯一约束“PK_Users”错误【英文标题】:Duplicate key value violates unique constraint "PK_Users" error in EntityFramework when trying to insert an entity with 1:N relationship 【发布时间】:2021-12-09 17:21:04 【问题描述】:

我正在构建一个 ASP.NET Web API 应用程序,我有两个实体,用户和设备。用户与设备具有一对多关系(用户拥有多个设备)。问题是,当我插入具有特定用户 ID 的新设备时,我正在使用的 Posgres 数据库中收到一个严重错误。我将首先向您展示我的实体:

public class User

    [Key]
    public int Id  get; set; 
    public string Email  get; set; 
    public string Password  get; set; 
    public string Name  get; set; 
    public DateTime BirthDate  get; set; 
    public string Address  get; set; 
    public string Role  get; set; 

    public ICollection<Device> Devices  get; set; 

    public User()
    
        Devices = new List<Device>();
    


public class Device

    [Key]
    public int Id  get; set; 
    public string Description  get; set; 
    public string Location  get; set; 
    public double MaxEnergyConsumption  get; set; 
    public double AverageEnergyConsumption  get; set; 

    public User User  get; set; 


无需显示捕获post请求以插入设备的控制器方法,该方法只需调用以下服务函数:

public async Task Insert(DeviceDTOWithoutId entity)

    var _deviceEntity = _mapper.Map<Device>(entity);

    var _userEntity = await _unitOfWork.Users.Get(q => q.Id == entity.UserId);

    _userEntity.Devices.Add(_deviceEntity);
    _deviceEntity.User = _userEntity;

    await _unitOfWork.Devices.Insert(_deviceEntity);
    await _unitOfWork.Save();

我正在使用带有工作单元的存储库模式。存储库中的通用 Insert 方法非常简单,并且适用于其他实体:

public async Task Insert(T entity)

    await _db.AddAsync(entity);

现在让我解释一下我的问题的细节。例如,我的数据库中有一个 ID 为 1 的用户。在 Swagger 中,我想插入以下设备,例如:


  "description": "Smart Sensor",
  "location": "Garage",
  "maxEnergyConsumption": 10,
  "averageEnergyConsumption": 5,
  "userId": 1 

我是说这个设备属于 ID 为 1 的用户。我得到的请求响应代码是 500 内部服务器错误,以及以下错误:

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details.

 ---> Npgsql.PostgresException (0x80004005): 23505: duplicate key value violates unique constraint "PK_Users"

   at Npgsql.NpgsqlConnector.<ReadMessage>g__ReadMessageLong|194_0(NpgsqlConnector connector, Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)

   at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)

   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)

   at Npgsql.NpgsqlCommand.ExecuteReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken)

   at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)

  Exception data:

    Severity: ERROR

    SqlState: 23505

    MessageText: duplicate key value violates unique constraint "PK_Users"

    Detail: Detail redacted as it may contain sensitive data. Specify 'Include Error Detail' in the connection string to include this information.

    SchemaName: public

    TableName: Users

    ConstraintName: PK_Users

    File: d:\pginstaller_13.auto\postgres.windows-x64\src\backend\access\nbtree\nbtinsert.c

    Line: 656

    Routine: _bt_check_unique

   --- End of inner exception stack trace ---

   at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(IEnumerable`1 commandBatches, IRelationalConnection connection, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IList`1 entriesToSave, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(DbContext _, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)

   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)

   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)

   at EnergyManagement.Data.Repository.UnitOfWork.Save() in C:\Users\timot\Desktop\EnergyManagement\EnergyManagement\Data\Repository\UnitOfWork.cs:line 31

   at EnergyManagement.Services.DeviceService.Insert(DeviceDTOWithoutId entity) in C:\Users\timot\Desktop\EnergyManagement\EnergyManagement\Services\DeviceService.cs:line 62

   at EnergyManagement.Controllers.DeviceController.InsertDevice(DeviceDTOWithoutId deviceDTO) in C:\Users\timot\Desktop\EnergyManagement\EnergyManagement\Controllers\DeviceController.cs:line 33

   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)

   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)

   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)

   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)

   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)

   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)

   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)

   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)



HEADERS

=======

Accept: */*

Accept-Encoding: gzip, deflate, br

Accept-Language: en-GB,en;q=0.5

Connection: close

Content-Length: 109

Content-Type: application/json

Host: localhost:44397

Referer: https://localhost:44397/swagger/index.html

Te: trailers

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0

origin: https://localhost:44397

sec-fetch-dest: empty

sec-fetch-mode: cors

sec-fetch-site: same-origin

sec-gpc: 1

当我插入一个用户 ID 不在数据库中的设备时,例如 10,它会创建一个 ID 为 10 且所有字段为空的新用户。调用 _context.SaveChangesAsync() 时会出现此错误。如果我要直接在 Postgres 中使用纯 SQL 将上面显示的数据插入数据库中,它可以正常工作。 EntityFramework 做错了,或者我做错了。什么可能是我的问题的原因?如果您需要更多信息,我很乐意提供,我迫切需要解决这个问题。谢谢!

编辑:我的第一次迁移如下所示:

public partial class firstMigration : Migration

    protected override void Up(MigrationBuilder migrationBuilder)
    
        migrationBuilder.CreateTable(
            name: "Users",
            columns: table => new
            
                Id = table.Column<int>(type: "integer", nullable: false)
                    .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
                Email = table.Column<string>(type: "text", nullable: true),
                Password = table.Column<string>(type: "text", nullable: true),
                Name = table.Column<string>(type: "text", nullable: true),
                BirthDate = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
                Address = table.Column<string>(type: "text", nullable: true),
                Role = table.Column<string>(type: "text", nullable: true)
            ,
            constraints: table =>
            
                table.PrimaryKey("PK_Users", x => x.Id);
            );

        migrationBuilder.CreateTable(
            name: "Devices",
            columns: table => new
            
                Id = table.Column<int>(type: "integer", nullable: false)
                    .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
                Description = table.Column<string>(type: "text", nullable: true),
                Location = table.Column<string>(type: "text", nullable: true),
                MaxEnergyConsumption = table.Column<double>(type: "double precision", nullable: false),
                AverageEnergyConsumption = table.Column<double>(type: "double precision", nullable: false),
                UserId = table.Column<int>(type: "integer", nullable: true),
                UserId1 = table.Column<int>(type: "integer", nullable: true),
                UserId2 = table.Column<int>(type: "integer", nullable: true)
            ,
            constraints: table =>
            
                table.PrimaryKey("PK_Devices", x => x.Id);
                table.ForeignKey(
                    name: "FK_Devices_Users_UserId",
                    column: x => x.UserId,
                    principalTable: "Users",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
                table.ForeignKey(
                    name: "FK_Devices_Users_UserId1",
                    column: x => x.UserId1,
                    principalTable: "Users",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
                table.ForeignKey(
                    name: "FK_Devices_Users_UserId2",
                    column: x => x.UserId2,
                    principalTable: "Users",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Restrict);
            );

        migrationBuilder.CreateIndex(
            name: "IX_Devices_UserId",
            table: "Devices",
            column: "UserId");

        migrationBuilder.CreateIndex(
            name: "IX_Devices_UserId1",
            table: "Devices",
            column: "UserId1");

        migrationBuilder.CreateIndex(
            name: "IX_Devices_UserId2",
            table: "Devices",
            column: "UserId2");
    

    protected override void Down(MigrationBuilder migrationBuilder)
    
        migrationBuilder.DropTable(
            name: "Devices");

        migrationBuilder.DropTable(
            name: "Users");
    

我已经像@Thyselius 所说的那样添加了导航属性,但并没有解决问题。这是添加后的迁移:

public partial class addedUserIdToDevice : Migration

    protected override void Up(MigrationBuilder migrationBuilder)
    
        migrationBuilder.DropForeignKey(
            name: "FK_Devices_Users_UserId",
            table: "Devices");

        migrationBuilder.AlterColumn<int>(
            name: "UserId",
            table: "Devices",
            type: "integer",
            nullable: false,
            defaultValue: 0,
            oldClrType: typeof(int),
            oldType: "integer",
            oldNullable: true);

        migrationBuilder.AddForeignKey(
            name: "FK_Devices_Users_UserId",
            table: "Devices",
            column: "UserId",
            principalTable: "Users",
            principalColumn: "Id",
            onDelete: ReferentialAction.Cascade);
    

    protected override void Down(MigrationBuilder migrationBuilder)
    
        migrationBuilder.DropForeignKey(
            name: "FK_Devices_Users_UserId",
            table: "Devices");

        migrationBuilder.AlterColumn<int>(
            name: "UserId",
            table: "Devices",
            type: "integer",
            nullable: true,
            oldClrType: typeof(int),
            oldType: "integer");

        migrationBuilder.AddForeignKey(
            name: "FK_Devices_Users_UserId",
            table: "Devices",
            column: "UserId",
            principalTable: "Users",
            principalColumn: "Id",
            onDelete: ReferentialAction.Restrict);
    

【问题讨论】:

这强烈表明_db.AddAsync(entity) 中的上下文与_unitOfWork.Users.Get 中使用的上下文不同。如果是这样,那应该修复,它破坏了unit of work 模式。 【参考方案1】:

作为一种解决方法,尝试从您的代码中删除 _userEntity.Devices.Add(_deviceEntity),但我不确定它是否会起作用

public async Task Insert(DeviceDTOWithoutId deviceDto)

     var user = await _unitOfWork.Users.Get(q => q.Id == deviceDto.UserId);
    if(user==null) return BadRequest("user is not found");

    var device = _mapper.Map<Device>(deviceDto);

    device.User = user;

    await _unitOfWork.Devices.Insert(device);
    await _unitOfWork.Save();

正如 Thyselius 已经推荐的那样,更好的方法是将 UserId 添加到您的 Device 类中

public int UserId get; set;

在此之后删除所有旧的迁移数据,并进行干净的数据库迁移

由于您的实体中有 UserId,因此插入代码会简单得多

public async Task Insert(DeviceDTOWithoutId deviceDto)

    var device = _mapper.Map<Device>(deviceDto);

    await _unitOfWork.Devices.Insert(device);
    await _unitOfWork.Save();

【讨论】:

做这一切使它工作。但在这里:_unitOfWork.Devices.Insert(_deviceEntity); , _deviceEntity 的 User 字段为空,后来当我要获取一个用户及其所有关联设备时,用户实体中的 Devices 字段也为空。 @OrosTom var user = await _unitOfWork.Users.Get(q => q.Id == deviceDto.UserId);设备.用户 = 用户;如果 user 为 null,则表示 UsrId 为 0 或错误。您必须发布视图才能检查错误。 @OrosTom 检查我更新的答案。我添加了一些验证 我是说您为我提供了一个简化的插入代码,其中不包括将设备添加到用户列表并且不将用户实体分配给设备。如果我这样做,我要插入的设备实体将具有它的常用字段,但用户字段将为空,这意味着设备实体没有分配给它的用户。当我要使用您提供的第一个代码时,当我将设备添加到用户列表以及将用户分配给设备实体时,我仍然会从 Postgres 收到 PK_Users 错误。用户实体检索工作正常。 @OrosTom 您的 deviceDto 中有 UserId,映射后您将在新设备类实例中拥有 UserId。您不需要整个用户实例。 UserId 很多并且是首选。【参考方案2】:

看起来您所做的一切都是正确的,但您的用户 - 设备关系可能不匹配。您可以尝试添加导航属性

public int UserID get; set;

到您的设备类。或者在您的 ApplicationDbContext 中指定与流式 API 的关系。您是否已经对此进行了配置,以及创建这些表的迁移看起来如何?

这确实应该是一个评论,但我没有那个代表

【讨论】:

我添加了导航属性,并没有解决问题。我已经编辑了我的帖子并添加了迁移。而且我不明白“指定ApplicationDbContext中的关系与流畅的API”是什么意思我是ASP.NET和EntityFramework的新手,你能给出一个代码示例来说明你的意思吗?谢谢 您可以看到您的第一次迁移看起来有点时髦,您的 Device 表有三个不同的 UserId 列。由于您似乎处于早期阶段,我建议您简单地删除所有迁移并重新开始,这次看看 EF 想要如何创建表,看看它是否看起来更好 fluent API 是一种更明确的定义关系的方式,见docs.microsoft.com/en-us/ef/core/modeling/…

以上是关于尝试插入具有 1:N 关系的实体时,重复键值违反 EntityFramework 中的唯一约束“PK_Users”错误的主要内容,如果未能解决你的问题,请参考以下文章

违反 PRIMARY KEY 约束。无法插入重复键

Golang:重复键值违反唯一约束

勺子插入 postgres 会产生“重复键值违反唯一约束”

违反 PRIMARY KEY 约束“[Table_id]”。使用实体框架时无法在对象“[Table_id]”中插入重复键

保存子实体的重复键值

IntegrityError:重复键值违反唯一约束