I am upgrading a .NET Core Web API from 2.2 to 3.1. When testing the ChangePasswordAsync
function, I receive the following error message:
Cannot update identity column 'UserId'.
I ran a SQL Profile and I can see that the Identity column is not included in the 2.2 UPDATE statement but it is in 3.1.
The line of code in question returns NULL, as opposed to success or errors, and is as follows:
objResult = await this.UserManager.ChangePasswordAsync(objUser, objChangePassword.OldPassword, objChangePassword.NewPassword);
The implementation of the ChangePasswordAsnyc
is as follows (code truncated for brevity).
Note: AspNetUsers extends 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));
}
The UserId
is included in the objResult, along with a lot of other fields, which is then returned at the end of the method. From what I can tell, without being able to step through the ChangePasswordAsync
method, the function updates all fields that are contained in the objUser
.
Question:
How do I suppress the identity column from being populated in the UPDATE statement that ChangePasswordAsnyc
is generating? Do I need to add an attribute in the model? Do I need to remove the UserId
from the objUser
before passing it into the ChangePasswordAsync
? Or, something else?
Bounty Question
I have created a custom user class that extends the IdentityUser
class. In this custom class, there is an additional IDENTITY column. In upgrading the .NET Core 2.2 to 3.1, the ChangePasswordAsync
function no longer works because the 3.1 method is trying to UPDATE this IDENTITY column whereas this does not happen in 2.1.
There was no code change other than upgrading an installing the relevant packages. The accepted answer needs to fix the problem.
UPDATE
Migrations are not used as this results in a forced marriage between the database and the Web API with it's associated models. In my opinion, this violated the separation of duties between database, API, and UI. But, that's Microsoft up to its old ways again.
I have my own defined ADD, UPDATE, and DELETE methods that use EF and set the EntityState. But, when stepping through the code for ChangePasswordAsync
, I do not see any of these functions called. It is as if ChangePasswordAsync
uses the base methods in EF. So, I do not know how to modify this behavior. from Ivan's answer.
Note: I did post a question to try and understand how the ChangePasswordAsync
method calls EF here [Can someone please explain how the ChangePasswordAsnyc method works?][1].
namespace 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.
}
}
namespace 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.
}
}
EntitiesRepo
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;
}
}
}