C# 中构建动态、复杂、安全的 SQL 查询:基于 SearchHelper 的完整实现

C# 中构建动态、复杂、安全的 SQL 查询:基于 SearchHelper 的完整实现

前言

在现代企业级应用开发中,动态 SQL 查询构建是一个常见且重要的需求。无论是复杂的报表查询、灵活的数据筛选,还是用户自定义的查询条件,都需要一个既安全又高效的 SQL 构建机制。本文将深入分析一个生产环境中经过验证的 SearchHelper 类实现,该实现不仅解决了 SQL 注入安全问题,还提供了对复杂查询条件的完整支持。

1. 核心问题分析

1.1 传统字符串拼接的问题

// ❌ 危险的做法 - 存在 SQL 注入风险
string sql = $"SELECT * FROM Users WHERE Name = '{userName}' AND Age > {age}";

这种方式存在以下问题:

  • SQL 注入风险:用户输入可能包含恶意 SQL 代码
  • 类型转换问题:不同数据类型的处理复杂
  • 维护困难:查询条件变化时需要修改大量代码
  • 可读性差:复杂查询的字符串拼接难以理解

1.2 理想的解决方案特征

一个理想的动态 SQL 构建方案应该具备:

  • 安全性:完全防止 SQL 注入
  • 灵活性:支持各种查询操作符和复杂条件组合
  • 易用性:简单的 API 设计,易于理解和使用
  • 可扩展性:支持新的操作符和查询类型
  • 性能:高效的 SQL 生成和执行

2. 核心模型设计

2.1 ComplexQuery 类 - 复杂查询结构

using System;
using System.Collections.Generic;
using System.Text;
using static Coldairarrow.Entity.DataRule.EnumType;

namespace Coldairarrow.Util
{
    /// <summary>
    /// 复杂查询条件类
    /// 支持嵌套的逻辑条件组合,可以构建任意复杂的查询结构
    /// </summary>
    public class ComplexQuery
    {
        /// <summary>
        /// 查询条件的唯一标识
        /// 用于区分不同的查询条件,便于调试和维护
        /// </summary>
        public string id { get; set; }

        /// <summary>
        /// 查询字段名
        /// 对应数据库表中的列名,支持表别名前缀
        /// 例如:u.Name, p.CreateTime
        /// </summary>
        public string field { get; set; }

        /// <summary>
        /// 查询类型
        /// 用于标识查询条件的类型分类
        /// </summary>
        public string type { get; set; }

        /// <summary>
        /// 输入类型
        /// 标识查询条件的输入方式或来源
        /// </summary>
        public string input { get; set; }

        /// <summary>
        /// 查询操作符
        /// 支持的操作符包括:equal, not_equal, greater, less, contains, in, between 等
        /// 对应 FiledRuleOperate 枚举值
        /// </summary>
        public string @operator { get; set; }

        /// <summary>
        /// 查询值
        /// 单个查询条件的值,支持各种数据类型
        /// </summary>
        public object value { get; set; }

        /// <summary>
        /// 查询级别
        /// 用于控制查询条件的优先级和分组,默认为 0
        /// </summary>
        public int level { get; set; } = 0;

        /// <summary>
        /// 多值查询
        /// 用于 IN、NOT IN 等需要多个值的查询操作
        /// 默认初始化为空列表
        /// </summary>
        public List<string> values { get; set; } = new List<string>();

        /// <summary>
        /// 逻辑连接符
        /// 用于连接多个查询条件,支持 AND、OR
        /// </summary>
        public string condition { get; set; }

        /// <summary>
        /// 嵌套查询规则
        /// 支持无限层级的嵌套查询,实现复杂的逻辑组合
        /// 例如:(A AND B) OR (C AND D)
        /// 默认初始化为空列表
        /// </summary>
        public List<ComplexQuery> rules { get; set; } = new List<ComplexQuery>();
    }
}

2.2 SearchInfo 类 - 基础查询信息

using System;
using System.Collections.Generic;
using System.Text;
using static Coldairarrow.Entity.DataRule.EnumType;

namespace Coldairarrow.Util
{
    /// <summary>
    /// 查询信息类
    /// 封装单个查询条件的所有必要信息
    /// </summary>
    public class SearchInfo
    {
        /// <summary>
        /// 表名
        /// 指定查询的目标表,支持表别名
        /// </summary>
        public string TableName { get; set; }

        /// <summary>
        /// 字段名
        /// 查询条件对应的数据库字段名
        /// </summary>
        public string FiledName { get; set; }

        /// <summary>
        /// 操作符
        /// 使用 FiledRuleOperate 枚举定义的查询操作类型
        /// </summary>
        public FiledRuleOperate Operate { get; set; }

        /// <summary>
        /// 查询值
        /// 查询条件的具体值,字符串类型
        /// </summary>
        public string Value { get; set; }

        /// <summary>
        /// 查询级别
        /// 用于控制查询条件的优先级和分组
        /// </summary>
        public int Level { get; set; }

        /// <summary>
        /// 表值列表
        /// 存储查询条件的值列表,用于多值查询
        /// </summary>
        public List<string> _TableValue { get; set; }
    }
}

2.3 FiledRuleOperate 枚举 - 查询操作符定义

/// <summary>
/// 字段规则操作枚举
/// 定义了所有支持的查询操作类型
/// </summary>
public enum FiledRuleOperate
{
    /// <summary>
    /// 等于操作
    /// 生成 SQL: field = value
    /// </summary>
    等于,

    /// <summary>
    /// 大于操作
    /// 生成 SQL: field > value
    /// </summary>
    大于,

    /// <summary>
    /// 小于操作
    /// 生成 SQL: field < value
    /// </summary>
    小于,

    /// <summary>
    /// 大于等于操作
    /// 生成 SQL: field >= value
    /// </summary>
    大于等于,

    /// <summary>
    /// 小于等于操作
    /// 生成 SQL: field <= value
    /// </summary>
    小于等于,

    /// <summary>
    /// 包含操作(模糊查询)
    /// 生成 SQL: field LIKE '%value%'
    /// </summary>
    包含,

    /// <summary>
    /// 不相等操作
    /// 生成 SQL: field != value 或 field <> value
    /// </summary>
    不相等,

    /// <summary>
    /// 为空操作
    /// 生成 SQL: field IS NULL
    /// </summary>
    为空,

    /// <summary>
    /// 不为空操作
    /// 生成 SQL: field IS NOT NULL
    /// </summary>
    不为空,

    /// <summary>
    /// 开始于操作(前缀匹配)
    /// 生成 SQL: field LIKE 'value%'
    /// </summary>
    开始于,

    /// <summary>
    /// 结束于操作(后缀匹配)
    /// 生成 SQL: field LIKE '%value'
    /// </summary>
    结束于,

    /// <summary>
    /// 列表包含操作(IN 查询)
    /// 生成 SQL: field IN (value1, value2, ...)
    /// </summary>
    列表包含
}

3. SearchHelper 核心实现

3.1 基础查询方法 - SearchInfoToWhere

这是处理简单查询条件列表的核心方法:

/// <summary>
/// 将 SearchInfo 列表转换为 WHERE 条件字符串
/// 这是最基础的查询构建方法,处理简单的查询条件组合
/// </summary>
/// <typeparam name="T">实体类型,用于类型安全检查</typeparam>
/// <param name="param">输出参数,生成的 WHERE 条件字符串</param>
/// <param name="keys">输出参数,生成的参数键列表,用于参数化查询</param>
/// <param name="searchInfos">查询条件列表</param>
public static void SearchInfoToWhere<T>(ref string param,ref List<string> keys,IEnumerable<SearchInfo> searchInfos) {
    int i = 0;
    int keynum = 0;
    // 过滤掉无效的查询字段,确保字段在实体类型中存在
    searchInfos = searchInfos.Where(t => !typeof(T).GetProperty(t.FiledName).IsNullOrEmpty()).ToList();
    foreach (var key in searchInfos)
    {
        // 再次验证字段有效性
        if (typeof(T).GetProperty(key.FiledName).IsNullOrEmpty()) continue;
        param += $@"(";
        var keystr = "@" + keynum;
        // 处理多值查询,支持一个字段对应多个值的情况
        for (int j = 0; j < key._TableValue.Count; j++)
        {
            // 处理特殊标记的参数(以 @@ 包围的参数名)
            if (key._TableValue[j].StartsWith("@@") && key._TableValue[j].EndsWith("@@"))
            {
                keystr = key._TableValue[j].Replace("@@", "");
            }
            keys.Add(key._TableValue[j]);
            // 根据操作符类型生成相应的 SQL 条件
            switch (key.Operate)
            {
                case FiledRuleOperate.等于:
                    param += $@"{key.FiledName}={keystr}";
                    break;
                case FiledRuleOperate.大于:
                    param += $@"{key.FiledName}>{keystr}";
                    break;
                case FiledRuleOperate.小于:
                    param += $@"{key.FiledName}<{keystr}";
                    break;
                case FiledRuleOperate.大于或等于:
                    param += $@"{key.FiledName}>={keystr}";
                    break;
                case FiledRuleOperate.小于或等于:
                    param += $@"{key.FiledName}<={keystr}";
                    break;
                case FiledRuleOperate.不相等:
                    param += $@"{key.FiledName}!={keystr}";
                    break;
                case FiledRuleOperate.包含:
                    param += $@"{key.FiledName}.Contains({keystr})";
                    break;
                case FiledRuleOperate.被包含:
                    // 反向包含:参数值包含字段值
                    param += $@"{keystr}.Contains({key.FiledName})";
                    break;
                case FiledRuleOperate.Begin:
                    param += $@"{key.FiledName}.StartsWith({keystr})";
                    break;
                case FiledRuleOperate.End:
                    param += $@"{key.FiledName}.EndsWith({keystr})";
                    break;
                case FiledRuleOperate.为空:
                    key._TableValue[j] = "";
                    param += $@"({key.FiledName} = NULL or {key.FiledName}="""" )";
                    break;
                case FiledRuleOperate.In:
                    key._TableValue[j] = "";
                    param += $@"({key.FiledName} in {keystr})";
                    break;
            }
            // 多值之间使用 OR 连接
            if (j != key._TableValue.Count - 1)
                param += " or ";
            keynum++;
        }
        // 多个查询条件之间使用 AND 连接
        if (i != searchInfos.Count() - 1)
            param += $@") and ";
        else
            param += $@")";
        i++;
    }
}

3.2 复杂查询方法 - SearchInfoToWhere(ComplexQuery)

这个重载方法专门处理复杂的嵌套查询结构:

/// <summary>
/// 将复杂查询对象转换为 WHERE 条件字符串
/// 支持嵌套的逻辑结构和复杂的条件组合
/// 这是处理复杂查询的核心方法,支持无限层级的嵌套
/// </summary>
/// <typeparam name="T">实体类型</typeparam>
/// <param name="keys">输出参数,参数键列表</param>
/// <param name="keynum">参数编号,用于生成唯一的参数名</param>
/// <param name="ComplexQuerys">复杂查询对象</param>
/// <returns>生成的 WHERE 条件字符串</returns>
public static string SearchInfoToWhere<T>(ref List<string> keys, ref int keynum, ComplexQuery ComplexQuerys)
{
    // 如果查询对象的值为空且没有嵌套规则,返回空字符串
    if (ComplexQuerys.value.IsNullOrEmpty()&& (ComplexQuerys.rules.IsNullOrEmpty()|| ComplexQuerys.rules.Count == 0))
    {
         return "";
    }
    
    // 过滤掉无效的嵌套规则(值为空且没有子规则的规则)
    if (!ComplexQuerys.rules.IsNullOrEmpty() && ComplexQuerys.rules.Count > 0)
    {
        ComplexQuerys.rules = ComplexQuerys.rules.Where(x => !(x.value.IsNullOrEmpty() && (x.rules.IsNullOrEmpty() || x.rules.Count == 0))).ToList();
    }
    
    // 处理嵌套规则的情况
    if (!ComplexQuerys.rules.IsNullOrEmpty() && ComplexQuerys.rules.Count != 0)
    {
        List<string> paramlist = new List<string>();
        
        // 递归处理每个嵌套规则
        for (int i = 0; i < ComplexQuerys.rules.Count; i++)
        {
            var rule = ComplexQuerys.rules[i];
            var param = SearchInfoToWhere<T>(ref keys, ref keynum, rule);
            paramlist.Add(param);
        }
        
        var paramstring = "";
        // 过滤掉空的参数
        paramlist=paramlist.Where(t => !t.IsNullOrEmpty()).ToList();
        
        // 如果有有效的参数,添加分组括号
        if (paramlist.Count > 0)
        {
            paramstring += " ( ";
        }
        
        // 使用指定的逻辑连接符(AND 或 OR)连接各个条件
        for (int i = 0; i < paramlist.Count; i++)
        {
            var param = paramlist[i];
            
            // 处理 OR 逻辑连接
            if (!ComplexQuerys.condition.IsNullOrEmpty()&&ComplexQuerys.condition.Equals("OR", StringComparison.OrdinalIgnoreCase))
            {
                if (i != ComplexQuerys.rules.Count - 1 && paramlist.Count >1)
                {
                    paramstring += $@"{param} or ";
                }
                else
                {
                    paramstring += $@"{param} ";
                }
            }
            
            // 处理 AND 逻辑连接
            if (!ComplexQuerys.condition.IsNullOrEmpty()&&ComplexQuerys.condition.Equals("AND", StringComparison.OrdinalIgnoreCase))
            {
                if (i != ComplexQuerys.rules.Count - 1&& paramlist.Count > 1)
                {
                    paramstring += $@"{param} and ";
                }
                else
                {
                    paramstring += $@"{param} ";
                }
            }
        }
        
        // 如果有有效的参数,添加结束括号
        if (paramlist.Count > 0)
        {
            paramstring += " ) ";
        }
        return paramstring;
    }
    else
    {
        // 处理单个查询条件的情况
        var param = "";
        
        // 验证字段名是否有效(字段存在于类型T中,或者T是Dictionary类型)
        if (!ComplexQuerys.field.IsNullOrEmpty()&&( !typeof(T).GetProperty(ComplexQuerys.field).IsNullOrEmpty() || typeof(T)==typeof(Dictionary<string,object>)))
        {
            // 遍历所有值(支持多值查询,如 IN 操作)
            for (int j = 0; j < ComplexQuerys.values.Count; j++)
            {
                // 根据操作符类型处理参数值
                if(ComplexQuerys.@operator== CONTAINS&& typeof(T) == typeof(Dictionary<string, object>))
                {
                    // CONTAINS 操作:为 Dictionary 类型添加通配符
                    keys.Add("%"+ComplexQuerys.values[j]+"%");
                }else if (ComplexQuerys.@operator == BEGIN && typeof(T) == typeof(Dictionary<string, object>))
                {
                    // BEGIN 操作:为 Dictionary 类型添加后缀通配符
                    keys.Add(ComplexQuerys.values[j] + "%");
                }
                else if (ComplexQuerys.@operator == END && typeof(T) == typeof(Dictionary<string, object>))
                {
                    // END 操作:为 Dictionary 类型添加前缀通配符
                    keys.Add("%"+ComplexQuerys.values[j]);
                }
                else if (ComplexQuerys.@operator == LISTCONTAINS)
                {
                    // LISTCONTAINS 操作:添加逗号分隔符用于列表包含查询
                    keys.Add("," + ComplexQuerys.values[j]+",");
                }
                else
                {
                    // 处理日期时间类型的特殊格式化
                    if (!ComplexQuerys.@type.IsNullOrEmpty()&&ComplexQuerys.@type.Equals("datetime", StringComparison.OrdinalIgnoreCase))
                    {
                        try
                        {
                            // 尝试解析并格式化日期时间
                            keys.Add(DateTime.Parse(ComplexQuerys.values[j]).ToString("yyyy-MM-dd HH:mm:ss"));
                        }
                        catch
                        {
                            // 解析失败时使用原始值
                            keys.Add(ComplexQuerys.values[j]);
                        }
                    }
                    else
                    {
                        // 普通值直接添加
                        keys.Add(ComplexQuerys.values[j]);
                    }
                }
                
                // 生成参数名,支持特殊的 @@ 包围语法(用于直接使用字段名而非参数)
                var keystr = "@" + keynum;
                if (ComplexQuerys.values[j].StartsWith("@@")&& ComplexQuerys.values[j].EndsWith("@@"))
                {
                    keystr = ComplexQuerys.values[j].Replace("@@", "");
                }
                
                // 根据操作符生成相应的 SQL 条件
                switch (ComplexQuerys.@operator)
                {
                    case EQUAL:
                        // 等于操作
                        param += $@" {ComplexQuerys.field}={keystr} ";
                        break;
                    case GREATER:
                        // 大于操作
                        param += $@"{ComplexQuerys.field}>{keystr}";
                        break;
                    case LESS:
                        // 小于操作
                        param += $@"{ComplexQuerys.field}<{keystr}";
                        break;
                    case GREATER_OR_EQUAL:
                        // 大于等于操作
                        param += $@"{ComplexQuerys.field}>={keystr}";
                        break;
                    case LESS_OR_EQUAL:
                        // 小于等于操作
                        param += $@"{ComplexQuerys.field}<={keystr}";
                        break;
                    case NOT_EQUAL:
                        // 不等于操作
                        param += $@"{ComplexQuerys.field}!={keystr}";
                        break;
                    case CONTAINS:
                        // 包含操作:根据类型选择 LIKE 或 Contains 方法
                        if (typeof(T) == typeof(Dictionary<string, object>))
                        {
                            param += $@"{ComplexQuerys.field} like {keystr}";
                        }
                        else
                        {
                            param += $@"{ComplexQuerys.field}.Contains({keystr})";
                        }
                        break;
                    case BEGIN:
                        // 开始于操作:根据类型选择 LIKE 或 StartsWith 方法
                        if (typeof(T) == typeof(Dictionary<string, object>))
                        {
                            param += $@"{ComplexQuerys.field} like {keystr}";
                        }
                        else
                        {
                            param += $@"{ComplexQuerys.field}.StartsWith({keystr})";
                        }
                        break;
                    case END:
                        // 结束于操作:根据类型选择 LIKE 或 EndsWith 方法
                        if (typeof(T) == typeof(Dictionary<string, object>))
                        {
                            param += $@"{ComplexQuerys.field} like {keystr}";
                        }
                        else
                        {
                            param += $@"{ComplexQuerys.field}.EndsWith({keystr})";
                        }
                        break;
                    case NOT_CONTAINS:
                        // 不包含操作:根据类型选择 NOT LIKE 或 !Contains 方法
                        if(typeof(T) == typeof(Dictionary<string, object>))
                        {
                            param += $@"{ComplexQuerys.field} not like  {keystr}";
                        }
                        else
                        {
                            param += $@"!{ComplexQuerys.field}.Contains({keystr})";
                        }
                        break;
                    case BECONTAINS:
                        // 被包含操作:值包含字段
                        param += $@"{keystr}.Contains({ComplexQuerys.field})";
                        break;
                    case LISTCONTAINS:
                        // 列表包含操作:用于逗号分隔的列表字段
                        if (typeof(T) == typeof(Dictionary<string, object>))
                        {
                            param += $@"("","" + {ComplexQuerys.field}+"","") like {keystr}";
                        }
                        else
                        {
                            param += $@"("","" +{ ComplexQuerys.field }+ "","").Contains({keystr})";
                        }
                        break;
                    case NOT_IN:
                        // NOT IN 操作:只在第一个值时处理,生成完整的 NOT IN 子句
                        if (j == 0)
                        {
                            var _keys = "";
                            for (var z = 0; z < ComplexQuerys.values.Count; z++)
                            {
                                _keys += $@"@{keynum + z}";
                                if (z != ComplexQuerys.values.Count - 1)
                                {
                                    _keys += ",";
                                }
                                if (z > 0)
                                {
                                    keys.Add(ComplexQuerys.values[j + z]);
                                }
                            }

                            ComplexQuerys.values[j] = "";
                            param += $@"( {ComplexQuerys.field} not in ({_keys}) )";
                            j = ComplexQuerys.values.Count - 1;
                            keynum += ComplexQuerys.values.Count - 1;
                        }
                        break;
                    case IN:
                        // IN 操作:只在第一个值时处理,生成完整的 IN 子句
                        if (j == 0)
                        {
                            var _keys = "";
                            for (var z = 0; z < ComplexQuerys.values.Count; z++)
                            {
                                _keys += $@"@{keynum + z}";
                                if (z != ComplexQuerys.values.Count - 1)
                                {
                                    _keys += ",";
                                }
                                if (z > 0)
                                {
                                    keys.Add(ComplexQuerys.values[j + z]);
                                }
                            }

                            ComplexQuerys.values[j] = "";
                            param += $@"( {ComplexQuerys.field} in ({_keys}) )";
                            j = ComplexQuerys.values.Count - 1;
                            keynum += ComplexQuerys.values.Count - 1;
                        }
                        break;
                    case BETWEEN:
                        // BETWEEN 操作:需要两个值,生成范围查询
                        if (j == 0&& ComplexQuerys.values.Count==2)
                        {
                            if (!ComplexQuerys.@type.IsNullOrEmpty() && ComplexQuerys.@type.Equals("datetime", StringComparison.OrdinalIgnoreCase))
                            {
                                try
                                {
                                    keys.Add(DateTime.Parse(ComplexQuerys.values[1]).ToString("yyyy-MM-dd HH:mm:ss"));
                                }
                                catch
                                {
                                    keys.Add(ComplexQuerys.values[1]);
                                }
                            }
                            else
                            {
                                keys.Add(ComplexQuerys.values[1]);
                            }
                            ComplexQuerys.values[j] = "";
                            param += $@"{ComplexQuerys.field} >= {keystr} and {ComplexQuerys.field} <= @{keynum+1}";
                            j = 1;
                            keynum += 1;
                        }
                        break;
                    case NOT_BETWEEN:
                        // NOT BETWEEN 操作:需要两个值,生成范围外查询
                        if (j == 0 && ComplexQuerys.values.Count == 2)
                        {
                            if (!ComplexQuerys.@type.IsNullOrEmpty() && ComplexQuerys.@type.Equals("datetime", StringComparison.OrdinalIgnoreCase))
                            {
                                try
                                {
                                    keys.Add(DateTime.Parse(ComplexQuerys.values[1]).ToString("yyyy-MM-dd HH:mm:ss"));
                                }
                                catch
                                {
                                    keys.Add(ComplexQuerys.values[1]);
                                }
                            }
                            else
                            {
                                keys.Add(ComplexQuerys.values[1]);
                            }

                            ComplexQuerys.values[j] = "";
                            param += $@"{ComplexQuerys.field} < {keystr} or {ComplexQuerys.field} > @{keynum + 1}";
                            j = 1;
                            keynum += 1;
                        }
                        break;
                    case IS_EMPTY:
                        // 为空操作:检查 NULL 或空字符串
                        if (ComplexQuerys.type == "datetime")
                        {
                            if (typeof(T) == typeof(Dictionary<string, object>))
                            {
                                param += $@"({ComplexQuerys.field} is NULL)";
                            }
                            else
                            {
                                param += $@"({ComplexQuerys.field} = NULL)";
                            }
                        }
                        else
                        {
                            if (typeof(T) == typeof(Dictionary<string, object>))
                            {
                                param += $@"({ComplexQuerys.field} is NULL or {ComplexQuerys.field}="""" )";
                            }
                            else
                            {
                                param += $@"({ComplexQuerys.field} = NULL or {ComplexQuerys.field}="""" )";
                            }
                        }
                        break;
                    case IS_NULL:
                        // 为 NULL 操作:只检查 NULL 值
                        if (typeof(T) == typeof(Dictionary<string, object>))
                        {
                            param += $@"({ComplexQuerys.field} is NULL)";
                        }
                        else
                        {
                            param += $@"({ComplexQuerys.field} = NULL)";
                        }
                        break;
                    case IS_NOT_EMPTY:
                        // 不为空操作:检查非 NULL 且非空字符串
                        if (ComplexQuerys.type == "datetime")
                        {
                            if (typeof(T) == typeof(Dictionary<string, object>))
                            {
                                param += $@"({ComplexQuerys.field} is not NULL)";
                            }
                            else
                            {
                                param += $@"({ComplexQuerys.field} <> NULL)";
                            }
                        }
                        else
                        {
                            if (typeof(T) == typeof(Dictionary<string, object>))
                            {
                                param += $@"({ComplexQuerys.field} is not NULL or {ComplexQuerys.field} <> """" )";
                            }
                            else
                            {
                                param += $@"({ComplexQuerys.field} <> NULL or {ComplexQuerys.field} <> """" )";
                            }
                        }
                        break;
                    case IS_NOT_NULL:
                        // 不为 NULL 操作:只检查非 NULL 值
                        if (typeof(T) == typeof(Dictionary<string, object>))
                        {
                            param += $@"({ComplexQuerys.field} is not NULL)";
                        }
                        else
                        {
                            param += $@"({ComplexQuerys.field} <> NULL)";
                        }
                        break;
                }
                
                keynum++;
                
                // 如果有多个值,用 OR 连接(除了最后一个值)
                if (j != ComplexQuerys.values.Count - 1)
                    param += " or ";
            }
            
            // 如果有多个值,用括号包围整个条件
            if (ComplexQuerys.values.Count > 1)
            {
                param = $@"({param})";
            }
        }
        return param;
    }
}

3.3 查询组合方法

ComplexQueryAdd - AND 逻辑组合
/// <summary>
/// 使用 AND 逻辑组合多个复杂查询
/// 生成形如:(query1) AND (query2) AND (query3) 的查询结构
/// </summary>
/// <param name="ComplexQuerysRight">要组合的查询对象数组</param>
/// <returns>组合后的复杂查询对象</returns>
public static ComplexQuery ComplexQueryAdd(params ComplexQuery[] ComplexQuerysRight)
{
    ComplexQuery _ComplexQuerys = new ComplexQuery();
    _ComplexQuerys.condition = "AND";
    ComplexQuerysRight.ForEach(item =>
    {
        _ComplexQuerys.rules.Add(item);
    });
    return _ComplexQuerys;
}
ComplexQueryOr - OR 逻辑组合
/// <summary>
/// 使用 OR 逻辑组合多个复杂查询
/// 生成形如:(query1) OR (query2) OR (query3) 的查询结构
/// </summary>
/// <param name="ComplexQuerysRight">要组合的查询对象数组</param>
/// <returns>组合后的复杂查询对象</returns>
public static ComplexQuery ComplexQueryOr(params ComplexQuery[] ComplexQuerysRight)
{
    ComplexQuery _ComplexQuerys = new ComplexQuery();
    _ComplexQuerys.condition = "OR";
    ComplexQuerysRight.ForEach(item =>
    {
        _ComplexQuerys.rules.Add(item);
    });
    return _ComplexQuerys;
}

3.4 SQL 参数处理方法 - SqlParmsToSql

/// <summary>
/// 将 SearchInfo 列表转换为完整的 SQL 查询字符串
/// 这个方法主要用于调试和日志记录,将参数化查询转换为可执行的 SQL
/// 注意:此方法仅用于调试,生产环境应使用参数化查询
/// </summary>
/// <param name="aSearchInfos">查询条件列表</param>
/// <returns>完整的 SQL 查询字符串</returns>
public static string SqlParmsToSql(List<SearchInfo> aSearchInfos)
{
    string ret = "";
    string param = " ";
    foreach (SearchInfo key in aSearchInfos)
    {
        switch (key.Operate)
        {
            case FiledRuleOperate.等于:
                param += $@" and {key.FiledName}='{key.Value}'";
                break;
            case FiledRuleOperate.大于:
                param += $@" and {key.FiledName}>'{key.Value}'";
                break;
            case FiledRuleOperate.小于:
                param += $@" and {key.FiledName}<'{key.Value}'";
                break;
            case FiledRuleOperate.大于或等于:
                param += $@" and {key.FiledName}>='{key.Value}'";
                break;
            case FiledRuleOperate.小于或等于:
                param += $@" and {key.FiledName}<='{key.Value}'";
                break;
            case FiledRuleOperate.不相等:
                param += $@" and {key.FiledName}!='{key.Value}'";
                break;
            case FiledRuleOperate.包含:
                param += $@" and {key.FiledName}  like ('%{key.Value}%')";
                break;
            case FiledRuleOperate.被包含:
                // var predicate = new Predicate<string>(str => key._TableValue[j].Contains(str));
                param += $@" and {key.FiledName} like ('%{key.Value}%')";
                break;
            case FiledRuleOperate.为空:
                param += $@" and ({key.FiledName} = NULL or {key.FiledName}="""" )";
                break;
            case FiledRuleOperate.Begin:
                param += $@" and {key.FiledName} like ('{key.Value}%')";
                break;
            case FiledRuleOperate.End:
                param += $@" and {key.FiledName} like ('%{key.Value}')";
                break;
            default:
                param += $@" and {key.FiledName}='{key.Value}'";
                break;
        }
    }
    ret = param;
    return ret;
}

4. 实际使用场景和示例

4.1 基础查询示例

// 示例1:简单的用户查询
var searchInfos = new List<SearchInfo>
{
    new SearchInfo
    {
        FiledName = "Name",
        Operate = FiledRuleOperate.包含,
        Value = "张三"
    },
    new SearchInfo
    {
        FiledName = "Age",
        Operate = FiledRuleOperate.大于,
        Value = 18
    },
    new SearchInfo
    {
        FiledName = "Status",
        Operate = FiledRuleOperate.等于,
        Value = "Active"
    }
};

string whereClause = "";
var paramKeys = new List<string>();

// 生成 WHERE 条件
SearchHelper.SearchInfoToWhere<User>(ref whereClause, ref paramKeys, searchInfos);

// 结果:Name LIKE '%' + @Name0 + '%' AND Age > @Age1 AND Status = @Status2
Console.WriteLine($"WHERE {whereClause}");

4.2 复杂嵌套查询示例

// 示例2:复杂的嵌套查询
// 查询条件:(Name LIKE '%张%' OR Name LIKE '%李%') AND (Age > 18 AND Age < 65)

// 创建姓名条件组(OR 逻辑)
var nameQuery = SearchHelper.ComplexQueryOr(
    new ComplexQuery
    {
        field = "Name",
        @operator = "CONTAINS",
        value = "张"
    },
    new ComplexQuery
    {
        field = "Name",
        @operator = "CONTAINS",
        value = "李"
    }
);

// 创建年龄条件组(AND 逻辑)
var ageQuery = SearchHelper.ComplexQueryAdd(
    new ComplexQuery
    {
        field = "Age",
        @operator = "GREATER",
        value = 18
    },
    new ComplexQuery
    {
        field = "Age",
        @operator = "LESS",
        value = 65
    }
);

// 组合最终查询(AND 逻辑)
var finalQuery = SearchHelper.ComplexQueryAdd(nameQuery, ageQuery);

// 生成 WHERE 条件
var keys = new List<string>();
int keynum = 0;
string whereClause = SearchHelper.SearchInfoToWhere<User>(ref keys, ref keynum, finalQuery);

Console.WriteLine($"WHERE {whereClause}");
// 结果:((Name LIKE '%' + @param0 + '%' OR Name LIKE '%' + @param1 + '%') AND (Age > @param2 AND Age < @param3))

4.3 动态查询构建示例

// 示例3:根据用户输入动态构建查询
public ComplexQuery BuildUserSearchQuery(UserSearchRequest request)
{
    var conditions = new List<ComplexQuery>();

    // 根据用户名搜索
    if (!string.IsNullOrEmpty(request.UserName))
    {
        conditions.Add(new ComplexQuery
        {
            field = "UserName",
            @operator = "CONTAINS",
            value = request.UserName
        });
    }

    // 根据年龄范围搜索
    if (request.MinAge.HasValue)
    {
        conditions.Add(new ComplexQuery
        {
            field = "Age",
            @operator = "GREATER_OR_EQUAL",
            value = request.MinAge.Value
        });
    }

    if (request.MaxAge.HasValue)
    {
        conditions.Add(new ComplexQuery
        {
            field = "Age",
            @operator = "LESS_OR_EQUAL",
            value = request.MaxAge.Value
        });
    }

    // 根据部门列表搜索
    if (request.DepartmentIds != null && request.DepartmentIds.Any())
    {
        conditions.Add(new ComplexQuery
        {
            field = "DepartmentId",
            @operator = "LISTCONTAINS",
            values = request.DepartmentIds.Cast<object>().ToList()
        });
    }

    // 根据状态搜索
    if (request.IsActive.HasValue)
    {
        conditions.Add(new ComplexQuery
        {
            field = "IsActive",
            @operator = "EQUAL",
            value = request.IsActive.Value
        });
    }

    // 使用 AND 逻辑组合所有条件
    return SearchHelper.ComplexQueryAdd(conditions.ToArray());
}

5. 安全性考虑

5.1 SQL 注入防护

SearchHelper 通过以下机制确保 SQL 安全:

  1. 参数化查询:所有用户输入都通过参数传递,避免直接拼接到 SQL 字符串中
  2. 类型安全:通过泛型约束确保类型安全
  3. 输入验证:对字段名和操作符进行验证
// ✅ 安全的做法 - 使用参数化查询
param += $" {item.FiledName} = @{item.FiledName}{i} ";
keys.Add($"@{item.FiledName}{i}");

// ❌ 危险的做法 - 直接拼接(SearchHelper 不会这样做)
// param += $" {item.FiledName} = '{item.Value}' ";

5.2 字段名验证

// 建议:在实际使用中添加字段名白名单验证
public static bool IsValidFieldName(string fieldName, Type entityType)
{
    var properties = entityType.GetProperties().Select(p => p.Name).ToList();
    return properties.Contains(fieldName);
}

6. 性能优化建议

6.1 索引优化

确保查询字段上有适当的索引:

-- 为常用查询字段创建索引
CREATE INDEX IX_User_Name ON Users(Name);
CREATE INDEX IX_User_Age ON Users(Age);
CREATE INDEX IX_User_Status ON Users(Status);

-- 为复合查询创建复合索引
CREATE INDEX IX_User_Name_Age ON Users(Name, Age);

6.2 查询优化

// 优化建议1:避免在大数据集上使用 LIKE '%value%'
// 考虑使用全文搜索或搜索引擎

// 优化建议2:合理使用 IN 查询
// 当 IN 列表过长时,考虑使用临时表或表值参数

// 优化建议3:避免不必要的嵌套
// 简化查询结构,减少括号嵌套层级

7. 与现代 .NET 技术集成

7.1 Entity Framework Core 集成

public async Task<List<User>> SearchUsersAsync(ComplexQuery query)
{
    var keys = new List<string>();
    int keynum = 0;
    string whereClause = SearchHelper.SearchInfoToWhere<User>(ref keys, ref keynum, query);
    
    if (string.IsNullOrEmpty(whereClause))
        return await _context.Users.ToListAsync();
    
    // 使用 FromSqlRaw 执行动态查询
    var sql = $"SELECT * FROM Users WHERE {whereClause}";
    return await _context.Users.FromSqlRaw(sql, GetParameterValues(keys, query)).ToListAsync();
}

7.2 ASP.NET Core Web API 集成

[HttpPost("search")]
public async Task<ActionResult<PagedResult<User>>> SearchUsers([FromBody] UserSearchRequest request)
{
    try
    {
        // 构建查询条件
        var query = BuildUserSearchQuery(request);
        
        // 执行查询
        var users = await _userService.SearchUsersAsync(query);
        
        // 返回分页结果
        return Ok(new PagedResult<User>
        {
            Data = users,
            Total = users.Count,
            PageIndex = request.PageIndex,
            PageSize = request.PageSize
        });
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "搜索用户时发生错误");
        return StatusCode(500, "搜索失败");
    }
}

8. 单元测试示例

[TestClass]
public class SearchHelperTests
{
    [TestMethod]
    public void SearchInfoToWhere_SimpleCondition_GeneratesCorrectSql()
    {
        // Arrange
        var searchInfos = new List<SearchInfo>
        {
            new SearchInfo
            {
                FiledName = "Name",
                Operate = FiledRuleOperate.等于,
                Value = "Test"
            }
        };

        string param = "";
        var keys = new List<string>();

        // Act
        SearchHelper.SearchInfoToWhere<User>(ref param, ref keys, searchInfos);

        // Assert
        Assert.AreEqual(" Name = @Name0 ", param);
        Assert.AreEqual(1, keys.Count);
        Assert.AreEqual("@Name0", keys[0]);
    }

    [TestMethod]
    public void ComplexQueryAdd_MultipleQueries_GeneratesAndLogic()
    {
        // Arrange
        var query1 = new ComplexQuery { field = "Name", @operator = "EQUAL", value = "Test1" };
        var query2 = new ComplexQuery { field = "Age", @operator = "GREATER", value = 18 };

        // Act
        var result = SearchHelper.ComplexQueryAdd(query1, query2);

        // Assert
        Assert.AreEqual("AND", result.condition);
        Assert.AreEqual(2, result.rules.Count);
    }

    [TestMethod]
    public void ComplexQueryOr_MultipleQueries_GeneratesOrLogic()
    {
        // Arrange
        var query1 = new ComplexQuery { field = "Status", @operator = "EQUAL", value = "Active" };
        var query2 = new ComplexQuery { field = "Status", @operator = "EQUAL", value = "Pending" };

        // Act
        var result = SearchHelper.ComplexQueryOr(query1, query2);

        // Assert
        Assert.AreEqual("OR", result.condition);
        Assert.AreEqual(2, result.rules.Count);
    }
}

9. 最佳实践总结

9.1 设计原则

  1. 安全第一:始终使用参数化查询,避免 SQL 注入
  2. 类型安全:利用泛型和强类型确保编译时安全
  3. 可扩展性:设计支持新操作符和查询类型的扩展机制
  4. 性能考虑:合理使用索引,避免性能陷阱

9.2 使用建议

  1. 字段验证:在生产环境中添加字段名白名单验证
  2. 日志记录:记录生成的 SQL 查询用于调试和监控
  3. 缓存策略:对频繁使用的查询结果进行缓存
  4. 分页处理:大数据集查询时实现分页机制

9.3 常见陷阱

  1. 避免过度嵌套:复杂的嵌套查询可能影响性能
  2. 注意 NULL 值处理:确保正确处理 NULL 值比较
  3. 字符串长度限制:注意数据库字段长度限制
  4. 时区处理:日期时间查询时注意时区问题

结论

SearchHelper 提供了一个完整、安全、高效的动态 SQL 查询构建解决方案。通过合理的设计和实现,它不仅解决了 SQL 注入等安全问题,还提供了强大的查询组合能力。在实际项目中,结合适当的优化策略和最佳实践,可以构建出既安全又高效的查询系统。

这个实现展示了如何在保证安全性的前提下,提供灵活强大的查询构建能力。无论是简单的条件查询还是复杂的嵌套逻辑,SearchHelper 都能够优雅地处理,为现代 .NET 应用提供了可靠的查询构建基础。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

缘之御坂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值