基于特性的ABP实体属性加解密通用实现方案求助
实体属性加解密的通用读取方案(关联实体场景)
问题描述
通过特性实现实体属性加解密,目前已在应用服务层处理DTO的加解密,也通过DbContext的SavingChanges事件实现了写入时的加密逻辑。但遇到关联实体场景(如Domain关联WindowsUser)时,需要在所有关联服务中重复编写解密逻辑,不想通过反射遍历所有实体子类来检查特性,希望找到读取时自动解密(含关联实体)的通用方案,比如利用实体的materialise事件处理。
示例实体代码:
public class WindowsUser : Entity, IMayHaveTenant { [Required] [StringLength(260)] public string Username { get; set; } [Encrypt] [Required] public string Password { get; set; } }
public class Domain : Entity, IMayHaveTenant { [Required] [StringLength(260)] public string DomainName { get; set; } public ICollection<WindowsUser> WindowsUsers { get; set; } public int? TenantId { get; set; } }
已实现的DbContext初始化:
public YourDbContext(DbContextOptions<YourDbContext> options) : base(options) { this.SavingChanges += DbContext_SavingChanges; }
可行方案
方案1:利用EF Core的Materialized事件(EF Core 5+)
订阅实体的Materialized事件,当实体从数据库加载完成后自动解密带[Encrypt]特性的属性,关联实体加载时也会触发该事件,无需在服务层额外处理。
修改DbContext实现:
public class YourDbContext : DbContext { public YourDbContext(DbContextOptions<YourDbContext> options) : base(options) { this.SavingChanges += DbContext_SavingChanges; // 为所有实体类型注册Materialized事件 foreach (var entityType in Model.GetEntityTypes()) { var materializerSource = entityType.GetService<IEntityMaterializerSource>(); materializerSource.Materialized += (_, args) => DecryptEntity(args.Entity); } } // 已有的写入加密逻辑 private void DbContext_SavingChanges(object? sender, SavingChangesEventArgs e) { foreach (var entry in ChangeTracker.Entries()) { if (entry.State is EntityState.Added or EntityState.Modified) { EncryptEntity(entry.Entity); } } } // 实体加密方法 private void EncryptEntity(object entity) { var encryptProps = entity.GetType().GetProperties() .Where(p => p.GetCustomAttribute<EncryptAttribute>() != null && p.PropertyType == typeof(string)); foreach (var prop in encryptProps) { var value = prop.GetValue(entity) as string; if (!string.IsNullOrEmpty(value)) { // 替换为你的安全加密实现 prop.SetValue(entity, Encrypt(value)); } } } // 实体解密方法 private void DecryptEntity(object entity) { var encryptProps = entity.GetType().GetProperties() .Where(p => p.GetCustomAttribute<EncryptAttribute>() != null && p.PropertyType == typeof(string)); foreach (var prop in encryptProps) { var value = prop.GetValue(entity) as string; if (!string.IsNullOrEmpty(value)) { // 替换为你的安全解密实现 prop.SetValue(entity, Decrypt(value)); } } } // 示例加密解密逻辑(请替换为AES等安全算法) private string Encrypt(string plainText) => Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText)); private string Decrypt(string cipherText) => Encoding.UTF8.GetString(Convert.FromBase64String(cipherText)); }
方案2:自定义值转换器(推荐,EF Core原生机制)
为带[Encrypt]特性的string属性注册值转换器,EF Core会自动在读写数据库时处理加解密,无需依赖事件,性能更优且代码更简洁。
步骤1:实现值转换器
public class EncryptedStringConverter : ValueConverter<string, string> { public EncryptedStringConverter(ConverterMappingHints? mappingHints = null) : base( plainText => Encrypt(plainText), cipherText => Decrypt(cipherText), mappingHints) { } private static string Encrypt(string plainText) { if (string.IsNullOrEmpty(plainText)) return plainText; // 替换为AES等安全加密算法实现 return Convert.ToBase64String(Encoding.UTF8.GetBytes(plainText)); } private static string Decrypt(string cipherText) { if (string.IsNullOrEmpty(cipherText)) return cipherText; // 替换为对应解密算法实现 return Encoding.UTF8.GetString(Convert.FromBase64String(cipherText)); } }
步骤2:在DbContext中全局注册
public class YourDbContext : DbContext { public YourDbContext(DbContextOptions<YourDbContext> options) : base(options) { // 不需要SavingChanges事件,值转换器会自动处理写入加密 } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 遍历所有实体属性,自动应用加密转换器 foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { foreach (var property in entityType.GetProperties()) { if (property.ClrType == typeof(string) && property.PropertyInfo?.GetCustomAttribute<EncryptAttribute>() != null) { property.SetValueConverter(new EncryptedStringConverter()); } } } } }
该方案优势:
- 完全由EF Core原生机制处理,主实体和关联实体的读写加解密自动完成,无需额外代码。
- 性能优于事件反射,值转换器是EF Core查询 pipeline的一部分,开销更低。
- 代码更简洁,无需维护SavingChanges和Materialized事件逻辑。
内容的提问来源于stack exchange,提问作者KeithMac
相关产品推荐
相关产品推荐

