.NET Core 3.1 ChangePasswordAsync Inner Exception "Cannot update Identity column"



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

无法更新标识列"UserId"。

我运行了一个SQL概要文件,可以看到Identity列不包括在2.2 UPDATE语句中,但它在3.1中。

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

objResult = await this.UserManager.ChangePasswordAsync(objUser, objChangePassword.OldPassword, objChangePassword.NewPassword);

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

UserId和许多其他字段一起包含在objResult中,然后在方法的末尾返回这些字段。据我所知,在不需要逐步执行ChangePasswordAsync方法的情况下,该函数会更新objUser中包含的所有字段。

问题:

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

赏金问题我创建了一个自定义用户类,扩展了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.
}
}

命名空间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.
}
}

实体回购

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

不确定这是什么时候发生的,但在最新的EF Core 2(2.2.6(中也存在相同的行为,也与下面的SO问题自动增量非键值实体框架核心2.0中的问题完全相同。因此,在我的答案中没有太多可以补充的:

这里的问题是,对于不属于键的标识列(即ValueGeneratedOnAdd(,AfterSaveBehaviorSave,这反过来又使Update方法将它们标记为已修改,这反过来会生成错误的UPDATE命令。

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

3.x中唯一的区别是,现在您有了GetAfterSaveBehaviorSetAfterSaveBehavior方法,而不是AfterSaveBehavior属性。要使EF Core始终将属性排除在更新之外,请将SetAfterSaveBehaviorPropertySaveBehavior.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); // <--
});
}

是否可以尝试使用"EntityState"属性将proptery标记为未修改?

db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.UserId).IsModified = false;
db.SaveChanges();

参见在实体框架中排除更新时的属性和https://learn.microsoft.com/en-us/ef/ef6/saving/change-tracking/entity-state

创建并运行迁移:

dotnet ef migrations add IdentityColumn
dotnet ef database update

相关内容

最新更新