.NET Core 3.1 ChangePasswordAsync 内部异常“无法更新标识列”

J W*_*ezy 6 c# entity-framework-core asp.net-core-identity .net-core-3.1

我正在将 .NET Core Web API 从 2.2 升级到 3.1。测试该ChangePasswordAsync功能时,我收到以下错误消息:

无法更新标识列“UserId”。

我运行了一个 SQL Profile,我可以看到 Identity 列不包含在 2.2 UPDATE 语句中,但它包含在 3.1 中。

有问题的代码行返回 NULL,而不是成功或错误,如下所示:

objResult = await this.UserManager.ChangePasswordAsync(objUser, objChangePassword.OldPassword, objChangePassword.NewPassword);
Run Code Online (Sandbox Code Playgroud)

的实现ChangePasswordAsnyc如下(为简洁起见,代码被截断)。

注意:AspNetUsers 扩展了 IdentityUser。

[HttpPost("/[controller]/change-password")]
public async Task<IActionResult> ChangePasswordAsync([FromBody] ChangePassword objChangePassword)
{
    AspNetUsers objUser = null; 
    IdentityResult objResult = null;    

    // retrieve strUserId from the token.

    objUser = await this.UserManager.FindByIdAsync(strUserId);
    objResult = await this.UserManager.ChangePasswordAsync(objUser, objChangePassword.OldPassword, objChangePassword.NewPassword);
    if (!objResult.Succeeded)
    {
        // Handle error.
    }

    return this.Ok(new User(objUser));
}
Run Code Online (Sandbox Code Playgroud)

UserId被包括在objResult,有很多其他领域,然后将其在该方法结束时返回的沿。据我所知,在无法逐步执行该ChangePasswordAsync方法的情况下,该函数会更新objUser.

题:

如何禁止在ChangePasswordAsnyc生成的 UPDATE 语句中填充标识列?我需要在模型中添加一个属性吗?在将其传递到 之前,我是否需要将其UserId从 中删除?或者是其他东西?objUserChangePasswordAsync

赏金问题 我创建了一个扩展IdentityUser类的自定义用户类。在这个自定义类中,还有一个额外的 IDENTITY 列。在将 .NET Core 2.2 升级到 3.1 时,该ChangePasswordAsync函数不再起作用,因为 3.1 方法正在尝试更新此 IDENTITY 列,而这在 2.1 中不会发生。

除了升级安装相关软件包之外,没有任何代码更改。接受的答案需要解决问题。

更新

不使用迁移,因为这会导致数据库和 Web API 及其关联模型之间的强制结合。在我看来,这违反了数据库、API 和 UI 之间的职责分离。但是,微软又回到了老路。

我有自己定义的 ADD、UPDATE 和 DELETE 方法,它们使用 EF 并设置 EntityState。但是,在单步执行 的代码时ChangePasswordAsync,我没有看到任何这些函数被调用。就好像ChangePasswordAsync使用 EF 中的基本方法一样。所以,我不知道如何修改这种行为。来自伊万的回答。

注意:我确实发布了一个问题来尝试了解该ChangePasswordAsync方法如何在此处调用 EF [有人可以解释一下 ChangePasswordAsnyc 方法是如何工作的吗?][1]。

命名空间 ABC.Model.AspNetCore

using ABC.Common.Interfaces;
using ABC.Model.Clients;
using Microsoft.AspNetCore.Identity;
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ABC.Model.AspNetCore
{
    // Class for AspNetUsers model
    public class AspNetUsers : IdentityUser
    {
        public AspNetUsers()
        {
            // Construct the AspNetUsers object to have some default values here.
        }

        public AspNetUsers(User objUser) : this()
        {
            // Populate the values of the AspNetUsers object with the values found in the objUser passed if it is not null.
            if (objUser != null)
            {
                this.UserId = objUser.UserId; // This is the problem field.
                this.Email = objUser.Email;
                this.Id = objUser.AspNetUsersId;
                // Other fields.
            }
        }

        // All of the properties added to the IdentityUser base class that are extra fields in the AspNetUsers table.
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        [Key]
        public int UserId { get; set; } 
        // Other fields.
    }
}
Run Code Online (Sandbox Code Playgroud)

命名空间 ABC.Model.Clients

using ABC.Model.AspNetCore;
using JsonApiDotNetCore.Models;
using System;
using System.ComponentModel.DataAnnotations;

namespace ABC.Model.Clients
{
    public class User : Identifiable
    {
        public User()
        {
            // Construct the User object to have some default values show when creating a new object.
        }

        public User(AspNetUsers objUser) : this()
        {
            // Populate the values of the User object with the values found in the objUser passed if it is not null.
            if (objUser != null)
            {
                this.AspNetUsersId = objUser.Id;
                this.Id = objUser.UserId;    // Since the Identifiable is of type Identifiable<int> we use the UserIdas the Id value.
                this.Email = objUser.Email;
                // Other fields.
            }
        }

        // Properties
        [Attr("asp-net-users-id")]
        public string AspNetUsersId { get; set; }

        [Attr("user-id")]
        public int UserId { get; set; }

        [Attr("email")]
        public string Email { get; set; }

        [Attr("user-name")]
        public string UserName { get; set; }

        // Other fields.
    }
}
Run Code Online (Sandbox Code Playgroud)

实体仓库

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace ABC.Data.Infrastructure
{  
    public abstract class EntitiesRepositoryBase<T> where T : class
    {
        #region Member Variables
        protected Entities m_DbContext = null;
        protected DbSet<T> m_DbSet = null;
        #endregion


        public virtual void Update(T objEntity)
        {
            this.m_DbSet.Attach(objEntity);
            this.DbContext.Entry(objEntity).State = EntityState.Modified;
        }   
    }
}
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 6

不确定这种情况何时发生,但最新的 EF Core 2 (2.2.6) 中存在相同的行为,并且与以下 SO 问题Autoincrementnonkeyvalueentityframeworkcore2.0中的问题完全相同。因此,我无法在我的答案中添加太多内容:

这里的问题是,对于不属于 key 一部分的ValueGeneratedOnAdd标识列(即),is反过来又使方法将它们标记为已修改,这又会生成错误的命令。AfterSaveBehaviorSaveUpdateUPDATE

要解决这个问题,您必须AfterSaveBehavior像这样设置(内部OnModelCreating覆盖):

...

AfterSaveBehavior3.x 中唯一的区别是现在你拥有的不是属性GetAfterSaveBehavior而是SetAfterSaveBehavior方法。要让 EF Core 始终从更新中排除该属性,请使用SetAfterSaveBehaviorwith PropertySaveBehavior.Ignore,例如

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;

...

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    
    modelBuilder.Entity<AspNetUsers>(builder =>
    {
        builder.Property(e => e.UserId).ValueGeneratedOnAdd()
            .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore); // <--
    });

}
Run Code Online (Sandbox Code Playgroud)