基于Entity Framework实现表记录驱动的动态查询生成器方案问询
基于Entity Framework实现动态查询生成器方案
一、可行的动态查询实现方式
你可以通过**表达式树(Expression Tree)**实现基于配置表的动态查询,核心思路是从配置表读取实体名、属性名,动态构建Linq查询的Where条件和Select投影,直接在EF层面完成查询,避免先拉取全量数据再处理。
具体实现步骤
读取配置表数据
假设你的配置表实体为QueryConfig,包含DataProvider(实体名)、FieldName(属性名)、FilterValue(过滤值,可选)等字段,先读取并分组:var configs = await db.QueryConfigs.ToListAsync(); // 按实体名分组,得到每个实体对应的查询字段和过滤条件 var entityConfigs = configs.GroupBy(c => c.DataProvider) .ToDictionary(g => g.Key, g => new { Fields = g.Select(c => c.FieldName).ToList(), Filters = g.Where(c => !string.IsNullOrEmpty(c.FilterValue)) .ToDictionary(c => c.FieldName, c => c.FilterValue) });动态获取DbSet
通过反射获取对应实体的DbSet:// 可替换为配置表中的DataProvider值 string targetEntity = "TM_ACC_Account"; var dbSetProperty = db.GetType().GetProperty(targetEntity); var dbSet = dbSetProperty?.GetValue(db) as IQueryable;动态构建Where条件
用表达式树拼接过滤条件(兼容你原代码中的固定条件):if (dbSet != null && entityConfigs.TryGetValue(targetEntity, out var config)) { var entityType = dbSet.ElementType; var parameter = Expression.Parameter(entityType, "item"); Expression whereExpr = null; // 遍历配置表中的过滤条件 foreach (var filter in config.Filters) { var property = Expression.Property(parameter, filter.Key); var value = Convert.ChangeType(filter.Value, property.Type); var constant = Expression.Constant(value); var equalExpr = Expression.Equal(property, constant); whereExpr = whereExpr == null ? equalExpr : Expression.AndAlso(whereExpr, equalExpr); } // 追加原代码中的固定过滤条件 var accCodeExpr = Expression.Equal(Expression.Property(parameter, "acc_code"), Expression.Constant(306)); var bckIdExpr = Expression.Equal(Expression.Property(parameter, "bck_id"), Expression.Constant(2)); whereExpr = Expression.AndAlso(whereExpr, Expression.AndAlso(accCodeExpr, bckIdExpr)); // 绑定Where方法到查询 var whereMethod = typeof(Queryable).GetMethods() .First(m => m.Name == "Where" && m.GetParameters().Length == 2) .MakeGenericMethod(entityType); dbSet = whereMethod.Invoke(null, new object[] { dbSet, Expression.Lambda(whereExpr, parameter) }) as IQueryable; }动态构建Select投影
基于配置的FieldName动态生成投影,减少数据传输:if (dbSet != null && entityConfigs.TryGetValue(targetEntity, out var config)) { var entityType = dbSet.ElementType; var parameter = Expression.Parameter(entityType, "a"); // 构建动态投影的属性绑定 var bindings = config.Fields.Select(field => { var property = Expression.Property(parameter, field); return Expression.Bind(CreateAnonymousType(config.Fields).GetProperty(field), property); }).ToList(); var newExpr = Expression.New(CreateAnonymousType(config.Fields).GetConstructor(Type.EmptyTypes), Enumerable.Empty<Expression>(), bindings); var selectLambda = Expression.Lambda(newExpr, parameter); // 绑定Select方法到查询 var selectMethod = typeof(Queryable).GetMethods() .First(m => m.Name == "Select" && m.GetParameters().Length == 2) .MakeGenericMethod(entityType, CreateAnonymousType(config.Fields)); dbSet = selectMethod.Invoke(null, new object[] { dbSet, selectLambda }) as IQueryable; } // 执行最终查询 var result = await dbSet?.ToListAsync();辅助方法:动态创建匿名类型
private static Type CreateAnonymousType(List<string> fieldNames) { var assemblyName = new AssemblyName("DynamicAnonymousTypes"); var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); var moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicModule"); var typeBuilder = moduleBuilder.DefineType($"AnonymousType_{Guid.NewGuid():N}", TypeAttributes.Public | TypeAttributes.Class); foreach (var field in fieldNames) { typeBuilder.DefineProperty(field, PropertyAttributes.None, typeof(object), null); var fieldBuilder = typeBuilder.DefineField($"_{field}", typeof(object), FieldAttributes.Private); // 生成get方法 var getMethodBuilder = typeBuilder.DefineMethod($"get_{field}", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, typeof(object), Type.EmptyTypes); var getIl = getMethodBuilder.GetILGenerator(); getIl.Emit(OpCodes.Ldarg_0); getIl.Emit(OpCodes.Ldfld, fieldBuilder); getIl.Emit(OpCodes.Ret); // 生成set方法 var setMethodBuilder = typeBuilder.DefineMethod($"set_{field}", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, null, new[] { typeof(object) }); var setIl = setMethodBuilder.GetILGenerator(); setIl.Emit(OpCodes.Ldarg_0); setIl.Emit(OpCodes.Ldarg_1); setIl.Emit(OpCodes.Stfld, fieldBuilder); setIl.Emit(OpCodes.Ret); } return typeBuilder.CreateType(); }
二、更优的动态SQL方案
如果表达式树实现过于繁琐,可选择以下两种简洁方案:
1. 动态Linq库简化实现
借助第三方动态Linq库(如Linq.Dynamic.Core),直接用字符串拼接查询条件和投影,无需手动构建表达式树:
// 安装对应NuGet包后使用 var query = db.TM_ACC_Account .Where("acc_code == @0 && bck_id == @1", 306, 2) .Select($"new ({string.Join(",", fieldMaps.Select(f => f.FieldName))})") .ToListAsync();
该方式支持参数化,可避免SQL注入,代码量极少。
2. EF Core原生动态SQL
若需完全控制SQL语句,使用EF Core的FromSqlRaw执行参数化动态SQL:
var fields = string.Join(",", fieldMaps.Select(f => f.FieldName)); var sql = $"SELECT {fields} FROM TM_ACC_Account WHERE acc_code = @p0 AND bck_id = @p1"; var result = await db.TM_ACC_Account .FromSqlRaw(sql, 306, 2) .ToListAsync();
必须使用参数化占位符,禁止直接拼接字符串,防止SQL注入。
三、现有代码优化建议
你当前代码先拉取全量符合条件的数据到内存再投影,会造成不必要的数据传输。建议将投影逻辑整合到EF的IQueryable查询中,让数据库只返回需要的字段:
// 优化后:投影在数据库层面执行 var query = await db.TM_ACC_Account .Where(item => item.acc_code == 306 && item.bck_id == 2) .Select(a => Extensions.Projection(a, fieldMaps)) // 确保该方法可被EF解析 .ToListAsync();
若Extensions.Projection包含EF无法解析的复杂逻辑,改用前文的动态投影方案即可。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

