能否在EF Core中使用泛型类型的DbSet,实现动态查询统一前缀表单表数据?
问题原因
- 泛型参数推导错误:调用
RestoreFormInfo时传入的参数是Type类型实例,导致泛型参数TEntity被识别为System.Type,你的DbContext中没有对应Type的DbSet,因此抛出异常。 - 实体类型获取错误:原有代码中
_context.GetType().GetProperty(formName).GetType()拿到的是PropertyInfo的类型,不是表单对应的实体类型。 - 异步逻辑未正确处理:
FirstAsync()返回的是Task<T>对象,你未等待执行完成就直接强转为IList,也会导致运行时错误。 - SQL注入风险:直接拼接SQL参数,存在安全隐患。
修复方案
首先是获取所有表单实体类型的逻辑:
// 方式1:通过DbContext模型获取,更稳定 var formEntityTypes = _context.Model.GetEntityTypes() .Where(t => t.ClrType.Name.StartsWith("Form")) .ToList(); // 方式2:如果要沿用你原来的反射属性逻辑 var formInfos = _context.GetType().GetProperties() .Where(p => p.Name.StartsWith("Form") && p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)) .Select(p => new { FormName = p.Name, EntityType = p.PropertyType.GetGenericArguments()[0] }) .ToList();
然后是遍历查询的完整实现:
foreach (var formTypeInfo in formEntityTypes) { var entityClrType = formTypeInfo.ClrType; // 获取非泛型DbSet var dbSet = _context.Set(entityClrType); // 构造参数化SQL,避免注入 var tableName = formTypeInfo.GetTableName(); var sql = $"SELECT * FROM {tableName} WHERE PacientID = @p0 AND AppointmentID = @p1"; var sqlParams = new object[] { pacientID, appointmentID }; // 反射调用FromSqlRaw var fromSqlMethod = typeof(RelationalQueryableExtensions) .GetMethods(BindingFlags.Public | BindingFlags.Static) .First(m => m.Name == nameof(RelationalQueryableExtensions.FromSqlRaw) && m.GetParameters().Length == 3); var genericFromSql = fromSqlMethod.MakeGenericMethod(entityClrType); var query = (IQueryable)genericFromSql.Invoke(null, new object[] { dbSet, sql, sqlParams }); // 添加无跟踪配置 query = query.AsNoTracking(); // 调用FirstAsync获取结果 var firstAsyncMethod = typeof(EntityFrameworkQueryableExtensions) .GetMethods(BindingFlags.Public | BindingFlags.Static) .First(m => m.Name == nameof(EntityFrameworkQueryableExtensions.FirstAsync) && m.GetParameters().Length == 1); var genericFirstAsync = firstAsyncMethod.MakeGenericMethod(entityClrType); var resultTask = (Task)genericFirstAsync.Invoke(null, new object[] { query }); await resultTask; // 拿到最终的表单实体对象 var formEntity = resultTask.GetType().GetProperty("Result").GetValue(resultTask); // 此处可以根据你的需求处理formEntity,比如存入集合统一返回 }
优化建议
如果所有表单实体都有相同的公共属性(比如PacientID、AppointmentID),可以定义一个公共接口让所有表单实体实现,比如:
public interface IAttendanceForm { public string PacientID { get; set; } public int AppointmentID { get; set; } }
拿到formEntity后可以直接转成IAttendanceForm处理公共属性,不需要额外反射。
内容的提问来源于stack exchange,提问作者Renan Oliveira
相关产品推荐
相关产品推荐

