在.NET应用开发中,数据唯一性校验是保障业务数据规范的重要环节。通过自定义特性标注需要校验唯一性的字段,再结合反射动态读取特性配置,能够实现一个通用的数据唯一性检查方案,减少重复代码量。

一、自定义唯一性校验特性
首先我们需要定义一个自定义特性,用来标记需要进行唯一性检查的实体类属性,同时可以配置一些校验相关的参数,比如校验提示信息、是否忽略空值等。
using System;
namespace UniqueCheckDemo.Attributes
{
/// <summary>
/// 标记属性需要进行唯一性校验的特性
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class UniqueCheckAttribute : Attribute
{
/// <summary>
/// 校验失败的提示信息
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// 是否忽略空值,默认忽略
/// </summary>
public bool IgnoreNull { get; set; } = true;
public UniqueCheckAttribute(string errorMessage = "")
{
ErrorMessage = errorMessage;
}
}
}二、模拟数据仓储与校验上下文
为了演示唯一性校验的逻辑,我们简单模拟一个数据仓储,用来模拟查询数据库中已存在的数据,实际项目中可以替换为真实的数据库查询逻辑。
using System;
using System.Collections.Generic;
using System.Linq;
namespace UniqueCheckDemo.Data
{
/// <summary>
/// 模拟用户数据实体
/// </summary>
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
/// <summary>
/// 模拟用户数据仓储
/// </summary>
public class UserRepository
{
// 模拟数据库已存在的用户数据
private readonly List<User> _existingUsers = new List<User>
{
new User { Id = 1, UserName = "zhangsan", Email = "zhangsan@ipipp.com" },
new User { Id = 2, UserName = "lisi", Email = "lisi@ipipp.com" }
};
/// <summary>
/// 检查指定属性值是否已存在
/// </summary>
/// <param name="propertyName">属性名称</param>
/// <param name="value">属性值</param>
/// <param name="excludeId">排除的实体ID,用于更新场景</param>
/// <returns>是否已存在</returns>
public bool IsPropertyValueExists(string propertyName, object value, int? excludeId = null)
{
if (value == null)
{
return false;
}
return propertyName switch
{
"UserName" => _existingUsers.Any(u => u.UserName == value.ToString() && (!excludeId.HasValue || u.Id != excludeId.Value)),
"Email" => _existingUsers.Any(u => u.Email == value.ToString() && (!excludeId.HasValue || u.Id != excludeId.Value)),
_ => false
};
}
}
}三、反射实现通用唯一性校验逻辑
接下来我们通过反射读取实体类上标记的特性,动态执行唯一性检查,返回校验结果。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UniqueCheckDemo.Attributes;
using UniqueCheckDemo.Data;
namespace UniqueCheckDemo.Validation
{
/// <summary>
/// 唯一性校验结果
/// </summary>
public class UniqueCheckResult
{
public bool IsSuccess { get; set; }
public List<string> ErrorMessages { get; set; } = new List<string>();
}
/// <summary>
/// 通用唯一性校验器
/// </summary>
public class UniqueValidator
{
private readonly UserRepository _userRepository = new UserRepository();
/// <summary>
/// 校验实体类的唯一性
/// </summary>
/// <param name="entity">待校验的实体</param>
/// <param name="excludeId">排除的实体ID,用于更新场景</param>
/// <returns>校验结果</returns>
public UniqueCheckResult ValidateUnique(object entity, int? excludeId = null)
{
var result = new UniqueCheckResult { IsSuccess = true };
Type entityType = entity.GetType();
// 获取实体类的所有属性
PropertyInfo[] properties = entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo property in properties)
{
// 获取属性上的UniqueCheckAttribute特性
var uniqueAttr = property.GetCustomAttribute<UniqueCheckAttribute>();
if (uniqueAttr == null)
{
continue;
}
// 获取属性值
object propertyValue = property.GetValue(entity);
// 如果忽略空值且属性值为空,跳过校验
if (uniqueAttr.IgnoreNull && propertyValue == null)
{
continue;
}
// 调用仓储检查方法判断值是否已存在
bool isExists = _userRepository.IsPropertyValueExists(property.Name, propertyValue, excludeId);
if (isExists)
{
result.IsSuccess = false;
string errorMsg = string.IsNullOrEmpty(uniqueAttr.ErrorMessage)
? $"{property.Name} 值已存在,请更换后重试"
: uniqueAttr.ErrorMessage;
result.ErrorMessages.Add(errorMsg);
}
}
return result;
}
}
}四、实际使用示例
下面我们定义一个用户实体类,在需要校验唯一性的属性上标记UniqueCheckAttribute特性,然后调用校验器完成校验。
using System;
using UniqueCheckDemo.Attributes;
using UniqueCheckDemo.Validation;
namespace UniqueCheckDemo
{
/// <summary>
/// 用户实体类
/// </summary>
public class UserDto
{
public int Id { get; set; }
[UniqueCheck(ErrorMessage = "用户名已存在,请更换其他用户名")]
public string UserName { get; set; }
[UniqueCheck(ErrorMessage = "邮箱已存在,请使用其他邮箱")]
public string Email { get; set; }
}
class Program
{
static void Main(string[] args)
{
var validator = new UniqueValidator();
// 测试新增场景:用户名已存在
var newUser = new UserDto
{
UserName = "zhangsan",
Email = "new_email@ipipp.com"
};
var addResult = validator.ValidateUnique(newUser);
Console.WriteLine($"新增校验结果:{addResult.IsSuccess}");
if (!addResult.IsSuccess)
{
foreach (var error in addResult.ErrorMessages)
{
Console.WriteLine(error);
}
}
// 测试更新场景:排除当前用户ID后校验
var updateUser = new UserDto
{
Id = 1,
UserName = "zhangsan",
Email = "zhangsan_new@ipipp.com"
};
var updateResult = validator.ValidateUnique(updateUser, updateUser.Id);
Console.WriteLine($"更新校验结果:{updateResult.IsSuccess}");
if (!updateResult.IsSuccess)
{
foreach (var error in updateResult.ErrorMessages)
{
Console.WriteLine(error);
}
}
}
}
}五、方案总结
这种基于特性和反射的实现方式,将唯一性校验的逻辑与业务代码完全解耦,后续如果需要新增校验的字段,只需要在对应的属性上添加UniqueCheckAttribute特性即可,不需要修改校验的核心逻辑。如果后续需要支持更多的校验规则,也可以扩展特性属性,在反射处理逻辑中增加对应的判断分支,具备很好的扩展性。
需要注意的是,反射操作会有一定的性能开销,如果对性能要求较高的场景,可以结合缓存机制,将反射获取的类型特性信息缓存起来,避免重复反射带来的性能损耗。