正则表达式特性在集成测试中未抛出异常的问题排查
问题:EF Core实体的正则验证未生效,纯空格输入通过了集成测试
我在做集成测试时遇到一个问题:我的ManualClassifier实体定义了RegularExpression数据注解,但当我给FirstName传入纯空格时,测试没有失败,数据反而被成功插入到数据库里了。
实体类代码
[Table("ManualClassifiers")] public class ManualClassifier : Classifier { [RegularExpression(@"^([a-zA-Z]+?)([-\s'][a-zA-Z]+)*?")] [Required(ErrorMessage = "First name field is mandatory")] [Column(name: "first_name")] public string FirstName { get; set; } [RegularExpression(@"^([a-zA-Z]+?)([-\s'][a-zA-Z]+)*?")] [Required(ErrorMessage = "Last name field is mandatory")] [Column(name: "last_name")] public string LastName { get; set; } [EmailAddress] [Required(ErrorMessage = "Email field is mandatory")] [Column(name: "email")] public string Email { get; set; } [RegularExpression(@"^[a-zA-Z0-9._]+$")] [Required(ErrorMessage = "Username field is mandatory")] [Column(name: "username")] public string Username { get; set; } [Required(ErrorMessage = "Password hash field is mandatory")] [Column(name: "passwordHash")] public string PasswordHash { get; set; } [Required(ErrorMessage = "Password salt field is mandatory")] [Column(name: "passwordSalt")] public string PasswordSalt { get; set; } }
测试代码
[TestMethod] public async Task Insert_ManualClassifier_With_Empty_Field_Fails() { using var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); var options = new DbContextOptionsBuilder<DataContext>() .UseSqlite(connection) .Options; using (var context = new DataContext(options)) { await context.Database.EnsureCreatedAsync(); } using (var context = new DataContext(options)) { var user = new ManualClassifier() { FirstName = " ", // 此处应为无效输入,测试应失败但实际通过 LastName = "Last", Email = "example@gmail.com", Username = "firstlast123" }; IRepositoryService repoService = new RepositoryService(context, NullLogger<RepositoryService>.Instance); string passwordHash = "pwhash"; string passwordSalt = "pwsalt"; int userID = await repoService.CreateUser(user, passwordHash, passwordSalt); } }
仓库方法代码
public async Task<int> CreateUser(ManualClassifier user, string passwordHash, string passwordSalt) { if (_context.ManualClassifiers.Any(u => u.Username == user.Username)) { _logger.LogDebug($"User with username {user.Username} already exists"); throw new UsernameAlreadyExistsException(user.Username); } if (_context.ManualClassifiers.Any(u => u.Email == user.Email)) { _logger.LogDebug($"User with email {user.Email} already exists"); throw new EmailAlreadyExistsException(user.Email); } user.PasswordHash = passwordHash; user.PasswordSalt = passwordSalt; user.Email = user.Email.ToLower(); _logger.LogDebug("New user created successfully"); await _context.ManualClassifiers.AddAsync(user); await _context.SaveChangesAsync(); return user.Id; }
原因分析
- EF Core不自动执行数据注解验证:
RegularExpression、Required这类数据注解,EF Core本身不会在SaveChangesAsync时触发验证逻辑。这些注解主要用于ASP.NET MVC/Web API的模型绑定阶段,或者需要手动调用验证API检查实体有效性。 - 正则表达式存在缺陷:当前正则
^([a-zA-Z]+?)([-\s'][a-zA-Z]+)*?没有以$结尾,意味着只要字符串开头匹配规则就会通过(不过本次问题的核心是验证根本未执行)。
解决方案
方案1:在仓库方法中手动验证实体
在保存前使用Validator类手动验证实体,验证失败则抛出异常:
using System.ComponentModel.DataAnnotations; public async Task<int> CreateUser(ManualClassifier user, string passwordHash, string passwordSalt) { // 新增实体验证逻辑 var validationContext = new ValidationContext(user); var validationResults = new List<ValidationResult>(); if (!Validator.TryValidateObject(user, validationContext, validationResults, validateAllProperties: true)) { var errors = string.Join("; ", validationResults.Select(r => r.ErrorMessage)); throw new InvalidOperationException($"Invalid user data: {errors}"); } // 原有重复检查逻辑 if (_context.ManualClassifiers.Any(u => u.Username == user.Username)) { _logger.LogDebug($"User with username {user.Username} already exists"); throw new UsernameAlreadyExistsException(user.Username); } if (_context.ManualClassifiers.Any(u => u.Email == user.Email)) { _logger.LogDebug($"User with email {user.Email} already exists"); throw new EmailAlreadyExistsException(user.Email); } user.PasswordHash = passwordHash; user.PasswordSalt = passwordSalt; user.Email = user.Email.ToLower(); _logger.LogDebug("New user created successfully"); await _context.ManualClassifiers.AddAsync(user); await _context.SaveChangesAsync(); return user.Id; }
方案2:在DbContext中全局验证
重写DbContext的SaveChangesAsync方法,统一验证所有新增/修改的实体:
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) { var entities = ChangeTracker.Entries() .Where(e => e.State == EntityState.Added || e.State == EntityState.Modified) .Select(e => e.Entity); foreach (var entity in entities) { var validationContext = new ValidationContext(entity); Validator.ValidateObject(entity, validationContext, validateAllProperties: true); } return await base.SaveChangesAsync(cancellationToken); }
方案3:修复正则表达式
给正则加上$结尾,确保整个字符串符合规则,同时避免纯空格输入:
// 修改后的正则,限制整个字符串必须匹配规则 [RegularExpression(@"^[a-zA-Z]+([-\s'][a-zA-Z]+)*$")]
内容的提问来源于stack exchange,提问作者Miguel Ferreira
相关产品推荐
相关产品推荐

