我正在将.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
(,AfterSaveBehavior
是Save
,这反过来又使Update
方法将它们标记为已修改,这反过来会生成错误的UPDATE
命令。要解决这个问题,您必须这样设置
AfterSaveBehavior
(在OnModelCreating
覆盖内(:
3.x中唯一的区别是,现在您有了GetAfterSaveBehavior
和SetAfterSaveBehavior
方法,而不是AfterSaveBehavior
属性。要使EF Core始终将属性排除在更新之外,请将SetAfterSaveBehavior
与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); // <--
});
}
是否可以尝试使用"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