You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 20:10:39