C#如何不通过继承按对象形状对泛型函数参数做类型检查?
方案1:使用partial类加公共接口(最优,完全保留编译时类型检查)
你的EF模型类已经标记为partial,不需要修改原有自动生成的类代码,只需要新增单独的代码文件,给需要的实体绑定公共接口即可:
首先定义公共属性接口:
public interface IAuditableEntity { DateTime CreatedTime { get; set; } string CreatorName { get; set; } }
然后在单独的代码文件中为实体类补充partial定义,实现接口:
// 不需要写任何额外代码,原有类已经有对应属性,自动匹配接口实现 public partial class BlogPost : IAuditableEntity {} public partial class Comment : IAuditableEntity {}
之后你的泛型方法就可以直接加接口约束:
public void SetInitialProperties<T>(T dbEntity, DateTime createdTime, string creatorName) where T : IAuditableEntity { dbEntity.CreatedTime = createdTime; dbEntity.CreatorName = creatorName; }
调用方式和你期望的完全一致,所有属性赋值都会在编译期做类型校验,不存在运行时风险。
方案2:EF Core 全局拦截自动赋值(免手动调用)
如果你用的是EF Core,可以直接重写DbContext的SaveChanges/SaveChangesAsync方法,在数据提交前自动给新增实体赋值,连手动调用SetInitialProperties的步骤都可以省略:
public override int SaveChanges() { var addedEntities = ChangeTracker.Entries() .Where(e => e.State == EntityState.Added) .Select(e => e.Entity); foreach (var entity in addedEntities) { // 检查实体是否有对应属性,有则自动赋值 var createdTimeProp = entity.GetType().GetProperty("CreatedTime"); if (createdTimeProp != null && createdTimeProp.PropertyType == typeof(DateTime)) { createdTimeProp.SetValue(entity, DateTime.UtcNow); } var creatorNameProp = entity.GetType().GetProperty("CreatorName"); if (creatorNameProp != null && creatorNameProp.PropertyType == typeof(string)) { creatorNameProp.SetValue(entity, 你的当前操作人变量); } } return base.SaveChanges(); }
如果担心反射性能,可以提前缓存所有实体类型的属性访问委托,开销可以忽略。
方案3:无接口场景下的类型安全实现
如果确实无法给实体类加接口,可以用表达式树编译出强类型的赋值委托,既不需要dynamic,也比普通反射性能好很多:
public static class EntityInitializer { public static void SetInitialProperties<T>(T entity, DateTime createdTime, string creatorName) { EntityAccessor<T>.SetCreatedTime(entity, createdTime); EntityAccessor<T>.SetCreatorName(entity, creatorName); } private static class EntityAccessor<T> { public static readonly Action<T, DateTime> SetCreatedTime = BuildPropertySetter<DateTime>(nameof(CreatedTime)); public static readonly Action<T, string> SetCreatorName = BuildPropertySetter<string>(nameof(CreatorName)); private static Action<T, TValue> BuildPropertySetter<TValue>(string propertyName) { var entityParam = Expression.Parameter(typeof(T), "entity"); var valueParam = Expression.Parameter(typeof(TValue), "value"); var propertyAccess = Expression.Property(entityParam, propertyName); var assignExpr = Expression.Assign(propertyAccess, valueParam); return Expression.Lambda<Action<T, TValue>>(assignExpr, entityParam, valueParam).Compile(); } } }
调用方式和之前一致,如果T没有对应属性,会在第一次调用时抛出明确的异常,比dynamic的隐式报错更安全,性能也接近原生赋值。
内容的提问来源于stack exchange,提问作者Benjamin U.
相关产品推荐
相关产品推荐

