You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET MVC中Fluent Validation重复校验失效及空引用异常解决

ASP.NET MVC Fluent Validation重复校验异常问题

问题详情

  • ASP.NET MVC项目中,Fluent Validation的必填字段校验规则均正常触发,但重复组合校验规则失效。需求是:当ProjectID、DocumentType和IsThisCurrent三者的组合已存在于数据库时,抛出重复错误(例如以下数据中第1行和第3行的组合重复):
Project ID DocumentType IsThisCurrent 
 1          Protocol     Yes 
 1          Application  Yes 
 1          Protocol     Yes 
 1          Protocol     No
  • 初始代码中校验时加载的是空列表var existingValues = new List<DocTrailList>();,无法读取数据库已有数据;尝试添加private ResearchContext _context;并修改为var existingValues = _context.DocTrailList!.ToList();后,触发**System.NullReferenceException: 'Object reference not set to an instance of an object'**异常。

相关代码

Create控制器代码

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("DetailID,ProjectID,DocumentType,Version,Dated,DateReceived,IsThisCurrent")] DocTrailList docTrailList)
{
    DocTrailListsValidator doctraillistsvalidator = new DocTrailListsValidator();
    ValidationResult result = doctraillistsvalidator.Validate(docTrailList);

    if (result.IsValid)
    {
        _context.Add(docTrailList);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    else
    {
        foreach (var failure in result.Errors)
        {
            ModelState.AddModelError(failure.PropertyName, failure.ErrorMessage);
        }
    }
    ViewData["ProjectID"] = new SelectList(_context.PIF, "ProjectID", "ProjectID", docTrailList.ProjectID);
    var dtname = _context.DocType!.ToList();
    ViewBag.dtname = dtname;

    var current = _context.YesNoList!.ToList();
    ViewBag.current = current;

    return View(docTrailList);
}

DocTrailListValidator代码

using FluentValidation;
using Research.Data;

namespace Research.Models
{
    public class DocTrailListsValidator : AbstractValidator<DocTrailList>
    {
        private ResearchContext _context;

        public DocTrailListsValidator()
        {
            RuleFor(x => x.DocumentType).NotNull().WithMessage("A required field");
            RuleFor(x => x.Version).NotNull().WithMessage("A required field");
            RuleFor(x => x.Dated).NotNull().WithMessage("A required field");
            RuleFor(x => x.DateReceived).NotNull().WithMessage("A required field");
            RuleFor(x => x.Version).NotNull().WithMessage("A required field");
            RuleFor(x => x.IsThisCurrent).NotNull().WithMessage("A required field");
            RuleFor(x => x.ProjectID)
            .Must((root, projectID, context) => !IsDuplicate(root))
            .WithMessage("Duplicate entry found.");
        }

        private bool IsDuplicate(DocTrailList c)
        {
            var existingValues = _context.DocTrailList!.ToList();
            return existingValues.Any(x => x.ProjectID == c.ProjectID &&
                                       x.DocumentType == c.DocumentType &&
                                       x.IsThisCurrent == c.IsThisCurrent);
        }
    }
}

Create视图校验代码

<div class="col-md-10">
     @Html.ValidationMessageFor(model => model.ProjectID, null, new { @class = "text-danger" })  
</div>

DocTrailList模型代码

using System.ComponentModel.DataAnnotations;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace Research.Models
{
    public class DocTrailList
    {
        [Key]
        public int DetailID { get; set; }
        public int? ProjectID { get; set; }
        
        [Display(Name = "Document Type")]
        public string? DocumentType { get; set; }
        
        public string? Version { get; set; }
        public string? Dated { get; set; }
        
        [Display(Name = "Date Received")]
        [DataType(DataType.Date)]
        public DateTime? DateReceived { get; set; }
        
        [Display(Name = "Is This Current?")]
        public string? IsThisCurrent { get; set; }
        
        [ForeignKey("ProjectID")]
        public PIF? PIF { get; set; }
    }
}

解决方案

1. 修复校验器的上下文初始化问题

异常原因是校验器中的_context字段未被赋值,需要通过构造函数传入ResearchContext实例:

using FluentValidation;
using Research.Data;

namespace Research.Models
{
    public class DocTrailListsValidator : AbstractValidator<DocTrailList>
    {
        // 改为readonly字段,确保不可修改
        private readonly ResearchContext _context;

        // 构造函数注入上下文
        public DocTrailListsValidator(ResearchContext context)
        {
            _context = context;
            
            // 保留原有必填校验规则(去重重复的Version规则)
            RuleFor(x => x.DocumentType).NotNull().WithMessage("必填字段");
            RuleFor(x => x.Version).NotNull().WithMessage("必填字段");
            RuleFor(x => x.Dated).NotNull().WithMessage("必填字段");
            RuleFor(x => x.DateReceived).NotNull().WithMessage("必填字段");
            RuleFor(x => x.IsThisCurrent).NotNull().WithMessage("必填字段");
            
            // 重复组合校验规则
            RuleFor(x => x.ProjectID)
                .Must((model, projectId) => !IsDuplicate(model))
                .WithMessage("已存在相同的项目ID、文档类型和当前状态组合");
        }

        private bool IsDuplicate(DocTrailList model)
        {
            // 直接查询数据库,避免加载全表,提升性能
            return _context.DocTrailList.Any(x => 
                x.ProjectID == model.ProjectID &&
                x.DocumentType == model.DocumentType &&
                x.IsThisCurrent == model.IsThisCurrent);
        }
    }
}

2. 控制器中传入上下文实例

修改控制器中校验器的初始化代码,将控制器已有的_context传入校验器:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("DetailID,ProjectID,DocumentType,Version,Dated,DateReceived,IsThisCurrent")] DocTrailList docTrailList)
{
    // 传入控制器的上下文实例
    DocTrailListsValidator doctraillistsvalidator = new DocTrailListsValidator(_context);
    ValidationResult result = doctraillistsvalidator.Validate(docTrailList);

    if (result.IsValid)
    {
        _context.Add(docTrailList);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    else
    {
        foreach (var failure in result.Errors)
        {
            ModelState.AddModelError(failure.PropertyName, failure.ErrorMessage);
        }
    }
    
    ViewData["ProjectID"] = new SelectList(_context.PIF, "ProjectID", "ProjectID", docTrailList.ProjectID);
    ViewBag.dtname = _context.DocType!.ToList();
    ViewBag.current = _context.YesNoList!.ToList();

    return View(docTrailList);
}

3. 性能优化(可选)

原代码中_context.DocTrailList!.ToList()会加载全表数据到内存,再进行判断,数据量大时性能低下。改为直接使用Any()方法,EF Core会生成对应的SQL查询,只检查是否存在匹配项,效率更高。

4. 依赖注入注册校验器(推荐)

如果项目使用依赖注入框架,建议在Program.cs中注册校验器:

builder.Services.AddScoped<DocTrailListsValidator>();

然后控制器通过构造函数注入校验器,无需手动实例化:

private readonly ResearchContext _context;
private readonly DocTrailListsValidator _validator;

// 构造函数注入上下文和校验器
public YourControllerName(ResearchContext context, DocTrailListsValidator validator)
{
    _context = context;
    _validator = validator;
}

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind(...)] DocTrailList docTrailList)
{
    ValidationResult result = _validator.Validate(docTrailList);
    // 后续逻辑不变
}

内容的提问来源于stack exchange,提问作者Sally Parkes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 09:15:54