【问题标题】:Can't add a claim with ASP.NET Core and identity无法使用 ASP.NET Core 和标识添加声明
【发布时间】:2023-03-10 11:04:02
【问题描述】:

我尝试向现有身份用户添加自定义声明,但在运行时出现异常:

Npgsql.PostgresException: 23502: null value in column "Id" violates not-null constraint

救命!

我做了什么。我使用以下命令行在 Windows 上创建了一个简单的 Web 应用程序

dotnet new mvc --auth Individual --framework netcoreapp1.1

我对 here 进行了更改,以使应用程序使用 PostgreSQL 作为数据库后端。创建的默认 webapp 工作正常。我可以注册为新用户、登录、注销等...

然后我修改了Home控制器的Test方法(我知道异常很难看):

    [Authorize]
    public async Task<IActionResult> Test()
    {
        var user = await GetCurrentUserAsync();
        if (user == null) {
            _logger.LogWarning("User is null.");
            throw new Exception("Not logged in");
        }
        _logger.LogWarning("User: {0}, {1}", user.Email, user);

        var claim = new Claim("TestClaimType", "TestClaimValue");

        IdentityResult idRes = IdentityResult.Failed();
        if (_userManager.SupportsUserClaim) {
            idRes = await _userManager.AddClaimAsync(user, claim); <------- Adding the claim
        }
        _logger.LogWarning("Return from adding claim");

        if (idRes != IdentityResult.Success) {
            throw new Exception("Failed to add claim.");
        }

        return View();
    }

登录后触发Test方法,得到如下日志(PostgresException接近尾声):

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://localhost:5000/Home/Test
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware[3]
      HttpContext.User merged via AutomaticAuthentication from authenticationScheme: Identity.Application.
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[1]
      Authorization was successful for user: mark@mark.com.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Executing action method AlumniConnect.Controllers.HomeController.Test (AlumniConnect) with arguments ((null)) - ModelState is Valid
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1]
      Executed DbCommand (1ms) [Parameters=[@__get_Item_0='?'], CommandType='Text', CommandTimeout='30']
      SELECT "e"."Id", "e"."AccessFailedCount", "e"."ConcurrencyStamp", "e"."Email", "e"."EmailConfirmed", "e"."LockoutEnabled", "e"."LockoutEnd", "e"."NormalizedEmail", "e"."NormalizedUserName", "e"."PasswordHash", "e"."PhoneNumber", "e"."PhoneNumberConfirmed", "e"."SecurityStamp", "e"."TwoFactorEnabled", "e"."UserName"
      FROM "AspNetUsers" AS "e"
      WHERE "e"."Id" = @__get_Item_0
      LIMIT 1
warn: AlumniConnect.Controllers.HomeController[0]
      User: mark@mark.com, mark@mark.com
warn: AlumniConnect.Controllers.HomeController[0]
      User: mark@mark.com, mark@mark.com
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1]
      Executed DbCommand (4ms) [Parameters=[@__normalizedUserName_0='?'], CommandType='Text', CommandTimeout='30']
      SELECT "u"."Id", "u"."AccessFailedCount", "u"."ConcurrencyStamp", "u"."Email", "u"."EmailConfirmed", "u"."LockoutEnabled", "u"."LockoutEnd", "u"."NormalizedEmail", "u"."NormalizedUserName", "u"."PasswordHash", "u"."PhoneNumber", "u"."PhoneNumberConfirmed", "u"."SecurityStamp", "u"."TwoFactorEnabled", "u"."UserName"
      FROM "AspNetUsers" AS "u"
      WHERE "u"."NormalizedUserName" = @__normalizedUserName_0
      LIMIT 1
info: Microsoft.EntityFrameworkCore.Storage.IRelationalCommandBuilderFactory[1]
      Executed DbCommand (33ms) [Parameters=[@p0='?', @p1='?', @p2='?', @p17='?', @p3='?', @p4='?', @p18='?', @p5='?', @p6='?', @p7='?', @p8='?', @p9='?', @p10='?', @p11='?', @p12='?', @p13='?', @p14='?', @p15='?', @p16='?'], CommandType='Text', CommandTimeout='30']
      INSERT INTO "AspNetUserClaims" ("ClaimType", "ClaimValue", "UserId")
      VALUES (@p0, @p1, @p2)
      RETURNING "Id";
      UPDATE "AspNetUsers" SET "AccessFailedCount" = @p3, "ConcurrencyStamp" = @p4, "Email" = @p5, "EmailConfirmed" = @p6, "LockoutEnabled" = @p7, "LockoutEnd" = @p8, "NormalizedEmail" = @p9, "NormalizedUserName" = @p10, "PasswordHash" = @p11, "PhoneNumber" = @p12, "PhoneNumberConfirmed" = @p13, "SecurityStamp" = @p14, "TwoFactorEnabled" = @p15, "UserName" = @p16
      WHERE "Id" = @p17 AND "ConcurrencyStamp" = @p18;
fail: Microsoft.EntityFrameworkCore.DbContext[1]
      An exception occurred in the database while saving changes.
      Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> Npgsql.PostgresException: 23502: null value in column "Id" violates not-null constraint
         at Npgsql.NpgsqlConnector.<DoReadMessageAsync>d__6.MoveNext()
      --- End of stack trace from previous location where exception was thrown ---

还有很多日志记录,但似乎没有添加新信息。我在整个日志中多次看到相同的异常。

我能做什么?这是 PostgreSQL 特有的问题吗?我是否试图以错误的方式添加声明?

谢谢!

【问题讨论】:

  • Npgsql.PostgresException: 23502: "Id" 列中的空值违反非空约束。似乎没有设置 [dbo].[AspNetUserClaims] 上的 Id。您可以检查用户对象上的 Id 吗?
  • 我无法检查这个。 Claim 类没有 ID 属性。我假设由于 UserManager 来自身份库,它会负责提供 ID。可能是代码期望自动生成一个 ID 而事实并非如此吗?嗯,'返回“Id”'似乎暗示了类似的东西。
  • 我假设未设置用户对象的 Id 属性,尽管该对象不为空。您能否在测试中设置断点并检查用户对象。
  • 对不起。这是不可能的。 “idRes = await _userManager.AddClaimAsync(user, claim);”添加声明后抛出异常并且不允许检查。我的怀疑是对的。 AspNetUserClaims 表没有自动递增的 Id 列。我会自己创建一个答案。

标签: postgresql asp.net-core asp.net-core-identity


【解决方案1】:

在 Identity 的迁移中,所有具有生成的 Integer id 的表都有一个用于添加自动生成此 id 的注释。此注解是特定于 SQL Server 的,例如 .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn)

解决此问题的方法是在迁移中添加 Postgres 特定的注释:.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn)。在旧版本的 Npgsql.EntityFrameworkCore.PostgreSQL 中,您可能需要使用 .Annotation("Npgsql:ValueGeneratedOnAdd", true)

用于创建 AspNetRoleClaims 表的迁移部分将如下所示:

            migrationBuilder.CreateTable(
            name: "AspNetRoleClaims",
            columns: table => new
            {
                Id = table.Column<int>(nullable: false)
                    .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn)
                    .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn),
                ClaimType = table.Column<string>(nullable: true),
                ClaimValue = table.Column<string>(nullable: true),
                RoleId = table.Column<string>(nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
                table.ForeignKey(
                    name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
                    column: x => x.RoleId,
                    principalTable: "AspNetRoles",
                    principalColumn: "Id",
                    onDelete: ReferentialAction.Cascade);
            });

【讨论】:

  • 这是一个比我更好的解决方案。谢谢。
【解决方案2】:

似乎在将声明添加到数据库时,SQL 语句的“RETURNING "Id"”子句建议返回 ID。但是,该表没有自动递增的 ID 列。

我按照Adding 'serial' to existing column in Postgres 的说明验证了这一点。

现在的问题当然是应该自动处理...

【讨论】:

    猜你喜欢
    • 2014-03-12
    • 2013-12-21
    • 1970-01-01
    • 1970-01-01
    • 2018-05-08
    • 1970-01-01
    • 2018-12-16
    • 2023-03-23
    • 1970-01-01
    相关资源
    最近更新 更多