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

C# EF Core如何通过字符串表名、字段名动态查询数据库数据

C# EF Core 动态传入表/视图名、字段名实现查询

问题背景

我在C#项目配置中存储了待取数的表/视图名称、对应字段名称,配置片段如下:

new()
{
    DisplayIndex = 1082,
    FieldNameDomain = nameof(VwScoringJobSupplier.SchemeIdentifications),
    Label = "Identifications",
    DataType = "string",
    Group = "Attributes",
    IsAnalysisFilter = false,
    IsUseCaseFilter = false,
    FilterType = "multiselect",
    IsAvailableForScoringAdmin = false,
    Source = "VwScoringJobSuppliers"
},

配置字段说明:

  • FieldNameDomain:存储查询目标字段名
  • Source:存储查询目标表/视图名

最初我写了固定查询VwScoringJobSuppliers视图指定字段的GetOptions方法,代码如下:

private async Task<List<OptionDto>> GetOptions(Guid jobId, string source, string field)
{
   var options = await _dbContext.VwScoringJobSuppliers.Distinct().Where(x => x.JobId == jobId)
        .Select(x => x.State).ToListAsync();
}

当前需求:查询使用的表/视图、Select选取的字段必须从字符串参数source、field动态传入。

已完成进度

目前已经实现了反射获取对应DbSet属性的工具方法,代码如下:

public static PropertyInfo GetDbSetProperty(Type typeOfEntity, ScoringDbContext context)
{
    var genericDbSetType = typeof(DbSet<>);
    var entityDbSetType = genericDbSetType.MakeGenericType(typeOfEntity);

    var contextType = context.GetType();

    return contextType
        .GetProperties(BindingFlags.Public | BindingFlags.Instance)
        .Where(p => p.PropertyType == entityDbSetType)
        .FirstOrDefault();      
}

当前卡点:不知道如何将获取到的DbSet结合到动态查询逻辑中,最初尝试的FromSqlRaw写法如下,但无法正常运行:

var blogs = context.Blogs
.FromSqlRaw("SELECT {0} FROM dbo.{1}", source, field)
.ToList();

具体实现方案

核心注意点

不要直接通过FromSqlRaw的参数化语法传表名、字段名:SQL参数仅支持传递值类型数据,传递数据库对象名(表、列)会被转义为字符串常量,导致SQL语法错误。表名、字段名必须做白名单校验后拼接,避免SQL注入风险。


方案1:EF Core 7.0+ 最简实现(推荐)

EF Core 7.0及以上版本支持Database.SqlQueryRaw<TResult>直接执行SQL返回标量结果,不需要绑定完整实体的DbSet,适配当前只查询单个字段去重值的场景:

  1. 先配置数据源白名单,启动时初始化,所有允许查询的表/视图都加入映射:
// 配置白名单,从根源避免SQL注入
private static readonly IReadOnlyDictionary<string, Type> SourceEntityMap = new Dictionary<string, Type>
{
    ["VwScoringJobSuppliers"] = typeof(VwScoringJobSupplier),
    // 后续新增的查询源在这里补充映射即可
};
  1. 实现动态查询逻辑:
private async Task<List<OptionDto>> GetOptions(Guid jobId, string source, string field)
{
    // 校验表名合法性
    if (!SourceEntityMap.ContainsKey(source))
        throw new ArgumentException($"不支持的数据源: {source}");
    
    // 校验字段合法性
    var entityType = SourceEntityMap[source];
    var fieldExist = entityType.GetProperty(field, BindingFlags.Public | BindingFlags.Instance) != null;
    if (!fieldExist)
        throw new ArgumentException($"数据源{source}不存在字段: {field}");

    // 白名单校验通过后拼接SQL,JobId通过参数化传入,无注入风险
    var sql = $"SELECT DISTINCT [{field}] FROM dbo.[{source}] WHERE JobId = @JobId";
    var fieldValues = await _dbContext.Database
        .SqlQueryRaw<string>(sql, new SqlParameter("@JobId", jobId))
        .ToListAsync();

    // 转换为OptionDto返回,根据你自己的DTO结构调整映射逻辑
    return fieldValues.Select(value => new OptionDto
    {
        Value = value,
        Label = value
    }).ToList();
}

方案2:兼容EF Core 6及以下版本(反射+表达式树)

如果使用低版本EF Core,可以复用你已经写好的GetDbSetProperty方法,通过动态构造表达式树实现强类型查询,不需要手写SQL:

private async Task<List<OptionDto>> GetOptions(Guid jobId, string source, string field)
{
    // 白名单校验逻辑和方案1一致
    if (!SourceEntityMap.TryGetValue(source, out var entityType))
        throw new ArgumentException($"不支持的数据源: {source}");
    var fieldProp = entityType.GetProperty(field, BindingFlags.Public | BindingFlags.Instance);
    if (fieldProp == null)
        throw new ArgumentException($"数据源{source}不存在字段: {field}");

    // 1. 反射获取对应DbSet
    var dbSetProp = GetDbSetProperty(entityType, _dbContext);
    var dbSet = dbSetProp.GetValue(_dbContext) as IQueryable;

    // 2. 构造 Where(x => x.JobId == jobId) 条件
    var param = Expression.Parameter(entityType, "x");
    var jobIdProp = entityType.GetProperty("JobId") 
        ?? throw new InvalidOperationException($"数据源{source}缺少JobId字段");
    var whereLambda = Expression.Lambda(
        Expression.Equal(
            Expression.Property(param, jobIdProp),
            Expression.Constant(jobId)
        ), param);
    var whereQuery = dbSet.Provider.CreateQuery(
        Expression.Call(typeof(Queryable), "Where", new[] { entityType }, dbSet.Expression, whereLambda)
    );

    // 3. 追加 Distinct()
    var distinctQuery = dbSet.Provider.CreateQuery(
        Expression.Call(typeof(Queryable), "Distinct", new[] { entityType }, whereQuery.Expression)
    );

    // 4. 构造 Select(x => x.目标字段) 投影
    var selectLambda = Expression.Lambda(Expression.Property(param, fieldProp), param);
    var selectQuery = distinctQuery.Provider.CreateQuery<string>(
        Expression.Call(typeof(Queryable), "Select", new[] { entityType, typeof(string) }, distinctQuery.Expression, selectLambda)
    );

    // 5. 执行查询并映射结果
    var fieldValues = await selectQuery.ToListAsync();
    return fieldValues.Select(value => new OptionDto
    {
        Value = value,
        Label = value
    }).ToList();
}

补充说明

  • 如果配置中DataType不是string类型,可以根据配置值动态调整SqlQueryRaw的泛型参数,或者在表达式树版本中增加类型转换逻辑。
  • 白名单校验不要省略,哪怕表名、字段名来自固定配置,也可以避免配置被恶意篡改后引发SQL注入问题。

内容的提问来源于stack exchange,提问作者Eugene Sukh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 23:21:41