EF Core:如何通过反射或KeyAttribute泛型获取实体主键值
批量实现Lookup实体主键映射(无需逐个修改策略类)
背景与需求
当前系统维护200余个数据库Lookup实体,每个实体包含带[Key]标记的主键、Code(命名规则多为[实体名]Code)和Description字段。原策略类仅返回Code和Description,现需新增主键映射到LookupBase的Id属性,但需避免逐个修改200+策略类。
现有代码示例
Lookup实体示例
public class FruitLookup { [Key] public int FruitId { get; set; } public string FruitCode { get; set; } public string Description { get; set; } }
原有策略类与基类
策略类示例:
public class FruitLookupStrategy : LookupStrategy { public IQueryable<LookupBase> GetLookupData() { return this.GetLookupData<FruitLookup>() .Select(_ => new LookupBase { Code = _.FruitCode, Description = _.Description }) .OrderBy(_ => _.Code) .AsQueryable(); } }
LookupBase类(已新增Id属性):
public class LookupBase { public int Id { get; set; } // 新增主键映射属性 public string Code { get; set; } public string Description { get; set; } }
基类LookupStrategy的查询方法:
public IList<T> GetLookupData<T>() { return this.context.Set<T>().AsNoTracking().ToList(); }
需要避免的逐个修改方式
public class FruitLookupStrategy : LookupStrategy { public IQueryable<LookupBase> GetLookupData() { return this.GetLookupData<FruitLookup>() .Select(_ => new LookupBase { Code = _.FruitCode, Description = _.Description, Id = _.FruitId }) .OrderBy(_ => _.Code) .AsQueryable(); } }
解决方案
方法1:泛型基类+EF元数据统一处理
改造基类为泛型抽象类,利用EF Core元数据自动识别主键、Code和Description字段,统一实现映射逻辑:
public abstract class LookupStrategy<TEntity> : LookupStrategy where TEntity : class { public override IQueryable<LookupBase> GetLookupData() { var dbSet = context.Set<TEntity>().AsNoTracking(); // 通过EF元数据获取主键属性 var pkProperty = context.Model.FindEntityType(typeof(TEntity)) .FindPrimaryKey().Properties.First(); // 匹配Code属性(优先按实体名前缀, fallback到"Code"字段) var entityName = typeof(TEntity).Name.Replace("Lookup", ""); var codeProperty = typeof(TEntity).GetProperty($"{entityName}Code") ?? typeof(TEntity).GetProperty("Code"); var descriptionProperty = typeof(TEntity).GetProperty("Description"); // 构建表达式树生成Select映射逻辑(EF可转换为SQL,避免客户端评估) var parameter = Expression.Parameter(typeof(TEntity), "x"); var idExpr = Expression.Property(parameter, pkProperty.Name); var codeExpr = Expression.Property(parameter, codeProperty.Name); var descExpr = Expression.Property(parameter, descriptionProperty.Name); var initExpr = Expression.MemberInit( Expression.New(typeof(LookupBase)), Expression.Bind(typeof(LookupBase).GetProperty(nameof(LookupBase.Id)), idExpr), Expression.Bind(typeof(LookupBase).GetProperty(nameof(LookupBase.Code)), codeExpr), Expression.Bind(typeof(LookupBase).GetProperty(nameof(LookupBase.Description)), descExpr) ); var selectLambda = Expression.Lambda<Func<TEntity, LookupBase>>(initExpr, parameter); return dbSet.Select(selectLambda) .OrderBy(x => x.Code) .AsQueryable(); } }
改造后策略类(无需重写方法):
public class FruitLookupStrategy : LookupStrategy<FruitLookup> { // 无额外代码,泛型基类已实现统一逻辑 }
方法2:扩展方法+反射/表达式树
若无法修改基类结构,可创建通用扩展方法统一处理映射:
public static class LookupExtensions { public static IQueryable<LookupBase> ToLookupBase<TEntity>(this IQueryable<TEntity> query, DbContext context) where TEntity : class { // 获取主键属性 var pkProperty = context.Model.FindEntityType(typeof(TEntity)) .FindPrimaryKey().Properties.First(); // 匹配Code属性 var entityName = typeof(TEntity).Name.Replace("Lookup", ""); var codeProperty = typeof(TEntity).GetProperty($"{entityName}Code") ?? typeof(TEntity).GetProperty("Code"); var descriptionProperty = typeof(TEntity).GetProperty("Description"); // 构建表达式树 var parameter = Expression.Parameter(typeof(TEntity), "x"); var idExpr = Expression.Property(parameter, pkProperty.Name); var codeExpr = Expression.Property(parameter, codeProperty.Name); var descExpr = Expression.Property(parameter, descriptionProperty.Name); var initExpr = Expression.MemberInit( Expression.New(typeof(LookupBase)), Expression.Bind(typeof(LookupBase).GetProperty(nameof(LookupBase.Id)), idExpr), Expression.Bind(typeof(LookupBase).GetProperty(nameof(LookupBase.Code)), codeExpr), Expression.Bind(typeof(LookupBase).GetProperty(nameof(LookupBase.Description)), descExpr) ); var selectLambda = Expression.Lambda<Func<TEntity, LookupBase>>(initExpr, parameter); return query.Select(selectLambda).OrderBy(x => x.Code); } }
改造后策略类:
public class FruitLookupStrategy : LookupStrategy { public IQueryable<LookupBase> GetLookupData() { return this.GetLookupData<FruitLookup>() .AsQueryable() .ToLookupBase(this.context); } }
优化建议
- 若Code属性命名无统一规则,可自定义
[LookupCode]特性标记属性,通过反射查找特性替代命名匹配; - 若主键类型不唯一(如int/string),可将
LookupBase.Id改为object类型,或创建泛型LookupBase<TId>支持不同主键类型; - 表达式树方式可确保EF将查询转换为SQL执行,避免客户端内存过滤,保障性能。
内容的提问来源于stack exchange,提问作者Donald N. Mafa
相关产品推荐
相关产品推荐

