FreeSql 动态查询条件转换为 SQL 语句的实现方案
背景介绍
在使用 FreeSql 进行数据库操作时,我们经常会遇到需要根据前端传入的动态条件构建 SQL 查询语句的需求。FreeSql 提供了 DynamicFilterInfo 类型来表示动态查询条件,但在直接执行原生 SQL 时,无法直接使用 WhereDynamicFilter 方法。本文将介绍如何将 DynamicFilterInfo 转换为 SQL 条件语句的完整解决方案。
核心实现思路
实现动态查询条件转换为 SQL 语句的核心思路是递归解析 DynamicFilterInfo 结构,并根据不同的操作符生成对应的 SQL 条件表达式。同时需要考虑参数化查询,防止 SQL 注入。
完整实现代码
以下是完整的实现代码,包含查询条件生成、参数处理以及排序条件生成等功能:
public class QueryHelper
{
public static (string, Dictionary<string, object>, string) GenerateWhereString(DynamicFilterInfo filterInfo)
{
string order = "Id desc";
var lstValues = new Dictionary<string, object>();
int index = 0;
var whereClause = GenerateWhereClause(filterInfo, ref index);
filterInfo.Travel((filterInfo) => lstValues.Add(lstValues.Count.ToString(), GetValue(filterInfo)));
return (whereClause, lstValues, order);
}
public static string GenerateOrder(QueryCondition queryCondition)
{
string order = "Id desc";
if (queryCondition.sort.Count > 0)
{
order = string.Empty;
foreach (var item in queryCondition.sort)
{
order += $"{item.field} {item.order},";
}
order = order.Trim(',');
}
return order;
}
static bool isArray = false;
static DateTime[] dates;
static bool isDate1 = false;
protected static string GenerateWhereClause(DynamicFilterInfo filterInfo, ref int index)
{
var lstConditions = new List<string>();
if (filterInfo.Value != null && filterInfo.Value.ToString().Trim() != string.Empty)
{
if (filterInfo.Value.GetType() == typeof(JArray))
{
isArray = true;
dates = Newtonsoft.Json.JsonConvert.DeserializeObject<DateTime[]>(filterInfo.Value.ToString());
isDate1 = true;
lstConditions.Add(ConvertToCondition(filterInfo, index++));
}
isArray = false;
lstConditions.Add(ConvertToCondition(filterInfo, index++));
}
if (filterInfo.Filters != null && filterInfo.Filters.Count > 0)
{
foreach (var subGroup in filterInfo.Filters)
{
if (subGroup.Value != null && subGroup.Value.ToString().Trim() != string.Empty)
{
lstConditions.Add(GenerateWhereClause(subGroup, ref index));
}
}
}
return $"({lstConditions.JoinAsString(filterInfo.Logic.ToString().ToLower() == "and" ? " and " : " or ")})";
}
protected static string ConvertToCondition(DynamicFilterInfo condition, int index)
{
string left = GetLeft(condition, index);
string right = GetRight(condition, index);
switch (condition.Operator.ToString().ToLower())
{
case "equals":
case "equal":
case "eq":
case "=":
case "==":
return $"{left} = {right}";
case "<>":
case "!=":
case "notequal":
return $"{left} <> {right}";
case "greater":
case "greaterthan":
case ">":
return $"{left} > {right}";
case "greaterorequal":
case "greaterthanorequal":
case ">=":
return $"{left} >= {right}";
case "less":
case "lessthan":
case "<":
return $"{left} < {right}";
case "lessorequal":
case "lessthanorequal":
case "<=":
return $"{left} <= {right}";
case "startwith":
return $"{left} like {right}";
case "notstartwith":
return $"{left} not like {right}";
case "endwith":
return $"{left} like {right}";
case "notendwith":
return $"{left} not like {right}";
case "contain":
case "contains":
case "like":
return $"{left} like {right}";
case "notcontains":
return $"{left} not like {right}";
case "range":
return $"{left} in({right})";
case "daterange":
if (isArray)
{
return $"{left} >={right}";
}
else
{
return $"{left} <={right}";
}
case "in":
case "any":
return $"{left} in({right})";
case "notany":
case "notcontain":
return $"{left} not in({right})";
default:
throw new InvalidOperationException($"Unknown dynamic query operator: {condition.Operator}");
}
}
protected static object GetValue(DynamicFilterInfo condition)
{
switch (condition.Operator.ToString().ToLower())
{
case "startwith":
case "notstartwith":
return $"{condition.Value}%";
case "endwith":
case "notendwith":
return $"%{condition.Value}";
case "contain":
case "contains":
case "like":
case "notcontains":
return $"%{condition.Value}%";
case "daterange":
if (isDate1)
{
isDate1 = false;
return dates[0];
}
else
{
return dates[1];
}
default:
return condition.Value;
}
}
protected static string GetLeft(DynamicFilterInfo condition, int index)
{
return condition.Field;
}
protected static string GetRight(DynamicFilterInfo condition, int index)
{
return $"@{index}";
}
}
public static class FilterExtensions
{
public static void Travel(this DynamicFilterInfo dynamicFilterInfo, Action<DynamicFilterInfo> conditionAction)
{
if (dynamicFilterInfo.Value != null && dynamicFilterInfo.Value.ToString().Trim() != string.Empty)
{
if (dynamicFilterInfo.Value.GetType() == typeof(JArray))
{
conditionAction(dynamicFilterInfo);
}
conditionAction(dynamicFilterInfo);
}
if (dynamicFilterInfo.Filters != null && dynamicFilterInfo.Filters.Count > 0)
{
foreach (var subGroup in dynamicFilterInfo.Filters)
{
Travel(subGroup, conditionAction);
}
}
}
public static string JoinAsString<T>(this IEnumerable<T> source, string separator)
{
return string.Join(separator, source);
}
}
使用示例
var (predicate, args, order) = QueryHelper.GenerateWhereString(search.FilterInfo);
var sql = "select * from table"; // 你的SQL语句
if (args.Count > 0)
{
if (sql.IndexOf("where", StringComparison.OrdinalIgnoreCase) > 0)
{
sql = sql + predicate;
}
else
{
sql = $"{sql} where {predicate}";
}
DbParameter[] cmdParms = _reportRepository.FreeSql.Ado.GetDbParamtersByObject(args);
var cmdType = System.Data.CommandType.Text;
var dynamics = _reportRepository.FreeSql.Ado.Query<dynamic>(cmdType, sql, cmdParms);
}
实现解析
-
递归解析查询条件:
GenerateWhereClause方法递归遍历DynamicFilterInfo结构,处理嵌套的查询条件。 -
操作符转换:
ConvertToCondition方法将各种操作符(如 equals、contains 等)转换为对应的 SQL 表达式。 -
参数处理:
GetValue方法根据不同的操作符对参数值进行适当处理,例如为 like 查询添加 % 通配符。 -
日期范围处理:特别处理了日期范围查询,支持数组形式的日期范围参数。
-
扩展方法:
FilterExtensions提供了遍历DynamicFilterInfo的扩展方法,方便收集所有参数值。
注意事项
-
此实现支持大多数常见的查询操作符,但可能需要根据实际需求进行扩展。
-
对于复杂的嵌套查询条件,确保逻辑运算符(AND/OR)的正确应用。
-
日期范围查询需要特殊处理,确保传入的参数是有效的日期数组。
-
使用参数化查询可以有效防止 SQL 注入攻击。
总结
通过上述实现,我们可以轻松地将 FreeSql 的动态查询条件转换为原生 SQL 语句,同时保持参数化查询的安全性。这种方法特别适用于需要直接执行原生 SQL 但又希望利用 FreeSql 动态查询功能的场景。开发者可以根据实际需求对代码进行扩展和优化,以适应更复杂的查询需求。
GLM-5智谱 AI 正式发布 GLM-5,旨在应对复杂系统工程和长时域智能体任务。Jinja00
GLM-5-w4a8GLM-5-w4a8基于混合专家架构,专为复杂系统工程与长周期智能体任务设计。支持单/多节点部署,适配Atlas 800T A3,采用w4a8量化技术,结合vLLM推理优化,高效平衡性能与精度,助力智能应用开发Jinja00
请把这个活动推给顶尖程序员😎本次活动专为懂行的顶尖程序员量身打造,聚焦AtomGit首发开源模型的实际应用与深度测评,拒绝大众化浅层体验,邀请具备扎实技术功底、开源经验或模型测评能力的顶尖开发者,深度参与模型体验、性能测评,通过发布技术帖子、提交测评报告、上传实践项目成果等形式,挖掘模型核心价值,共建AtomGit开源模型生态,彰显顶尖程序员的技术洞察力与实践能力。00
Kimi-K2.5Kimi K2.5 是一款开源的原生多模态智能体模型,它在 Kimi-K2-Base 的基础上,通过对约 15 万亿混合视觉和文本 tokens 进行持续预训练构建而成。该模型将视觉与语言理解、高级智能体能力、即时模式与思考模式,以及对话式与智能体范式无缝融合。Python00
MiniMax-M2.5MiniMax-M2.5开源模型,经数十万复杂环境强化训练,在代码生成、工具调用、办公自动化等经济价值任务中表现卓越。SWE-Bench Verified得分80.2%,Multi-SWE-Bench达51.3%,BrowseComp获76.3%。推理速度比M2.1快37%,与Claude Opus 4.6相当,每小时仅需0.3-1美元,成本仅为同类模型1/10-1/20,为智能应用开发提供高效经济选择。【此简介由AI生成】Python00
Qwen3.5Qwen3.5 昇腾 vLLM 部署教程。Qwen3.5 是 Qwen 系列最新的旗舰多模态模型,采用 MoE(混合专家)架构,在保持强大模型能力的同时显著降低了推理成本。00- RRing-2.5-1TRing-2.5-1T:全球首个基于混合线性注意力架构的开源万亿参数思考模型。Python00