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

自定义Unique校验属性中ValidationContext始终为Null的解决方法

修复自定义UniqueAttribute中validationContext为Null的问题

问题原因

validationContext为Null通常是因为验证触发时未传入依赖注入上下文:要么是手动调用验证方法时省略了ValidationContext参数,要么是验证流程未在ASP.NET Core的依赖注入容器环境中执行。即使你设置了RequiresValidationContext = true,如果验证触发方式不正确,框架也不会传入上下文。


核心修复步骤

1. 完善UniqueAttribute的实现(确保能正确获取上下文与服务)

修改自定义Attribute,通过validationContext解析DbContext实现唯一性校验,同时明确指定要校验的实体和属性:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class UniqueAttribute : ValidationAttribute
{
    private readonly Type _dbContextType;
    private readonly string _entitySetName;
    private readonly string _targetPropertyName;

    // 构造函数:指定DbContext类型、实体集合名称、要校验的属性名
    public UniqueAttribute(Type dbContextType, string entitySetName, string targetPropertyName)
    {
        _dbContextType = dbContextType;
        _entitySetName = entitySetName;
        _targetPropertyName = targetPropertyName;
        ErrorMessage = $"该{_targetPropertyName}已被占用";
    }

    public override bool RequiresValidationContext => true;

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null)
            return ValidationResult.Success;

        // 从validationContext获取DbContext实例
        var dbContext = validationContext.GetService(_dbContextType);
        if (dbContext == null)
            return new ValidationResult("无法获取数据库上下文");

        // 获取对应的实体集合
        var dbSet = dbContext.GetType().GetProperty(_entitySetName)?.GetValue(dbContext) as IQueryable;
        if (dbSet == null)
            return new ValidationResult($"找不到实体集合{_entitySetName}");

        // 通过表达式树构建查询,检查值是否存在
        var entityType = dbSet.ElementType;
        var parameter = Expression.Parameter(entityType, "entity");
        var property = Expression.Property(parameter, _targetPropertyName);
        var constantValue = Expression.Constant(value);
        var equalsExpr = Expression.Equal(property, constantValue);
        var lambda = Expression.Lambda(equalsExpr, parameter);

        var anyMethod = typeof(Queryable).GetMethods()
            .First(m => m.Name == "Any" && m.GetParameters().Length == 2)
            .MakeGenericMethod(entityType);

        var exists = (bool)anyMethod.Invoke(null, new object[] { dbSet, lambda });

        return exists ? new ValidationResult(ErrorMessage) : ValidationResult.Success;
    }
}

2. 正确使用Attribute

在模型类中指定DbContext、实体和属性信息:

public class Category
{
    // 传入ApplicationDbContext类型、实体集合名、要校验的属性名
    [Unique(typeof(ApplicationDbContext), nameof(Category), nameof(Name))]
    public string Name { get; set; }
    public string Description { get; set; }
}

3. 确保验证在依赖注入环境中触发

  • MVC/API场景:确保控制器标记了[ApiController]属性,ASP.NET Core会自动在模型绑定后触发验证,并传入完整的ValidationContext。
  • 手动验证场景:如果需要手动验证模型,必须手动创建ValidationContext并传入服务提供者:
    // 从依赖注入容器获取IServiceProvider
    var serviceProvider = ...; 
    var category = new Category { Name = "测试分类" };
    var validationContext = new ValidationContext(category, serviceProvider, null);
    var validationResults = new List<ValidationResult>();
    var isValid = Validator.TryValidateObject(category, validationContext, validationResults, validateAllProperties: true);
    
  • EF Core保存时验证:如果要在SaveChanges时触发验证,需要在DbContext中手动创建ValidationContext:
    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        var serviceProvider = this.GetService<IServiceProvider>();
        foreach (var entry in ChangeTracker.Entries()
            .Where(e => e.State is EntityState.Added or EntityState.Modified))
        {
            var validationContext = new ValidationContext(entry.Entity, serviceProvider, null);
            Validator.ValidateObject(entry.Entity, validationContext, validateAllProperties: true);
        }
        return await base.SaveChangesAsync(cancellationToken);
    }
    

内容的提问来源于stack exchange,提问作者Mahesh Kantariya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 14:18:39