EF Core/.NET Core中Identity表TPH多用户继承登录报错排查
TPH继承下ApplicationUser登录触发SqlNullValueException问题排查
项目背景与代码实现
基于.NET Core + SQL Server,采用数据库优先模式实现TPH表继承,实体类定义如下:
public class ApplicationUser: IdentityUser { public string FirstName { get; set; } public string LastName { get; set; } } public class Employer : ApplicationUser { public string employerName { get; set; } public string employerWebsite { get; set; } }
HomeController中创建Employer的代码:
private readonly UserManager<Employer> customerManager; // 注:原代码中UserManager<Application>为笔误 public HomeController(UserManager<Employer> _customerManager) { customerManager = _customerManager; } public async Task<ActionResult> IndexAsync() { var emp = new Employer() { Email = "customer1@hotmail.com", UserName = "Tom", employerName = "Harry", employerWebsite = "ghfhgf@hhh.com" }; var result = await customerManager.CreateAsync(emp, "Password01!"); }
问题现象
创建Employer实例时功能正常,AspNetUsers表中Discriminator列正确填充,但使用SignInManager<ApplicationUser>登录时抛出以下异常:
SqlNullValueException: Data is Null. This method or property cannot be called on Null values. Microsoft.Data.SqlClient.SqlDataReader.GetString(int i) ... Microsoft.AspNetCore.Identity.SignInManager<TUser>.PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure) ArevnaHireWebApp.Areas.Identity.Pages.Account.LoginModel.OnPostAsync(string returnUrl) in Login.cshtml.cs var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
登录页核心代码:
public class LoginModel : PageModel { private readonly SignInManager<ApplicationUser> _signInManager; private readonly ILogger<LoginModel> _logger; public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger) { _signInManager = signInManager; _logger = logger; } public class InputModel { [Display(Name = "Title")] [Required(ErrorMessage = "Title is required")] public int TitleID { get; set; } [Required] [DataType(DataType.Text)] [Display(Name = "First Name")] public string FirstName { get; set; } [Required] [DataType(DataType.Text)] [Display(Name = "Last Name")] public string LastName { get; set; } [Required] [EmailAddress] [Display(Name = "Email")] public string Email { get; set; } [Required] [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "Password")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "Confirm password")] [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] public string ConfirmPassword { get; set; } } public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); } // 省略后续代码 } }
原因分析
- 核心触发点:
ApplicationUser中的FirstName和LastName为非可空字符串(.NET 6+默认开启Nullable参考类型,string默认不可空),但创建Employer时未给这两个字段赋值,导致数据库中对应字段为NULL。 - 登录时的冲突:
SignInManager<ApplicationUser>查询用户数据时,EF Core尝试将数据库中的NULL值赋值给非可空的FirstName/LastName属性,触发SqlNullValueException。
解决方法
方法1:将ApplicationUser的字段改为可空类型
修改ApplicationUser类,标记FirstName和LastName为可空:
public class ApplicationUser: IdentityUser { public string? FirstName { get; set; } public string? LastName { get; set; } }
- 数据库优先模式下,需确保数据库中这两个字段允许NULL;若使用Code-First,需生成并执行迁移更新数据库结构。
方法2:创建Employer时强制赋值FirstName和LastName
修改创建逻辑,确保必填字段被赋值:
var emp = new Employer() { Email = "customer1@hotmail.com", UserName = "Tom", FirstName = "Tom", LastName = "Smith", employerName = "Harry", employerWebsite = "ghfhgf@hhh.com" };
方法3:关闭Nullable参考类型(不推荐)
在项目的.csproj文件中关闭Nullable检查:
<PropertyGroup> <Nullable>disable</Nullable> </PropertyGroup>
此方法会失去编译时空引用检查能力,仅作为临时解决方案。
内容的提问来源于stack exchange,提问作者Titan
相关产品推荐
相关产品推荐

