天下醉丶的个人博客

  • 首页
  • GPT
  • 随笔
  • 笔记
相顾无相识,长歌怀采薇
  1. 首页
  2. 技术
  3. .NET
  4. 正文

基于 Dapper 的SQL 帮助类

2026年2月4日 47点热度 0条评论
简介

一个基于 Dapper 的静态工具类,专为简化 Oracle 数据库的常规 CRUD 操作而设计。

它通过 “特性驱动(Attribute-Driven)” 的模式,利用反射机制将 C# 对象模型(DTO)动态转化为高性能、参数化的 SQL 语句。

主要特性:

  • 🚀 零 SQL 编写 针对单表的增、删、改、查、总数统计等操作,无需手写任何 SQL 片段,完全由 DTO 结构自动生成。

  • 🛡️ 自动参数化(防注入) 深度集成 Dapper 的 DynamicParameters,所有查询条件均通过占位符(如 :param)传递,从根源杜绝 SQL 注入风险。

  • 🔍 调试极其友好 所有构建方法均返回 SqlInfo 对象,其中的 DebugSql 属性会将参数还原为可执行的 SQL 字符串,方便直接复制到 PL/SQL 中排查逻辑。

  • ⚙️ 灵活的元数据映射 支持自定义表名映射、列名映射、主键识别,允许在 DTO 上直接定义复杂的 Oracle 函数。

实体类定义 (DTO 配置)

特性 作用 必填 说明
[Table("Name")] 映射数据库表名 是 如果不加,调用方法时需显式传入表名参数。
[Key] 标识主键 是 用于 UPDATE 的 WHERE 条件及 INSERT 的序列判断。
[Column("Name")] 映射数据库列名 否 属性名与列名一致时可省略。
[NotMapped] 排除字段 否 该属性完全不参与 SQL 生成(如辅助字段)。
[Select("...")] 虚拟计算列 否 用于 SELECT 查询时的函数列(如格式化日期、子查询),不参与保存。
[Query("...")] 虚拟查询列 否 用于 SELECT 查询时的函数的WHERE条件过滤,通常用于时间的等条件

 

更新说明

2026年2月4日 | V1.0 

版本说明:实现 Dapper 与实体模型的初步解耦,解决基础 CRUD 自动构建问题。

  • 核心功能实现:

    • 基础映射: 支持 [Table]、[Column]、[Key]、[NotMapped] 特性,建立 DTO 与物理表的映射。

    • 通用构建: 实现了 BuildQuery、BuildSave、BuildDelete 等核心方法。

    • 简单函数: 通过 [SqlFunction] 提供基础的 SQL 函数计算列支持。

    • 基础缓存: 引入 ConcurrentDictionary 缓存反射属性,优化高频调用性能。

2026年2月8日 | V2.0 

版本说明:深度适配 Oracle 复杂业务,从“代码驱动”转向“特性声明驱动”,极大强化了查询灵活性。

  • 新增功能与改进:

    • 引入 [Query] 特性 (重大更新):

      • 支持在 DTO 上直接通过特性声明 LIKE、IN、>、IS NULL 等 SQL 操作符。

      • 增强: 实现了“定义即逻辑”,彻底取代了旧版手动传入 likeFields 数组的繁琐操作。

    • 重构计算列体系 [Select]:

      • 原 [SqlFunction] 升级为 [Select],语义更契合 SQL SELECT 逻辑。

      • 增强: 支持 Oracle 包名函数调用(如 pkg_util.fn_get_info)及函数内参数占位符映射。

    • 安全性深度加固:

      • 通配符转义: 自动处理模糊查询中的 Oracle 特有转义字符(ESCAPE '\'),修复通配符导致的数据逻辑 Bug。

      • 安全黑名单: 在构建计算列和自定义片段时,增加 DROP、EXEC 等危险指令的强制检测。

    • 调试体验升级:

      • 重写 ToDebugSql 工具,针对 Oracle 环境自动完成 to_date 时间戳转换与布尔值转换,确保生成的调试 SQL 在数据库客户端 100% 原样执行。

    • 代码逻辑瘦身:

      • 提高了 BuildWhere 片段的解析速度。

声明

using Dapper;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

常量定义

        /// <summary>
        /// IN 子句最大条数限制。某些数据库(如 Oracle)对 IN 子句的参数个数有限制。
        /// </summary>
        private const int MaxInClauseSize = 1000;

        /// <summary>
        /// 基础排除名单。这些属性通常属于分页基类,用于程序控制而非数据库物理列。
        /// </summary>
        public static readonly string[] BaseExcludes = 
        { 
            "pageNumber", "pageSize", "totalCount", "sortField", "sortOrder",
            "Page", "PageSize", "TotalCount", "SortField", "SortOrder"
        };

缓存管理

        /// <summary>
        /// 线程安全的反射属性缓存。
        /// Key: DTO 类型完全限定名
        /// Value: 属性信息数组
        /// 用于降低高频反射带来的性能开销。初始容量 256,并发度为处理器数量 × 2。
        /// </summary>
        private static readonly ConcurrentDictionary<string, PropertyInfo[]> PropertyCache 
            = new(Environment.ProcessorCount * 2, 256);

        /// <summary>
        /// 表名缓存。
        /// Key: DTO 类型完全限定名
        /// Value: 表名
        /// </summary>
        private static readonly ConcurrentDictionary<string, string?> TableNameCache 
            = new(Environment.ProcessorCount * 2, 128);

        /// <summary>
        /// 清空所有缓存。在动态修改类定义或特性时调用此方法。
        /// </summary>
        public static void ClearCaches()
        {
            PropertyCache.Clear();
            TableNameCache.Clear();
        }

返回实体类

        /// <summary>
        /// SQL 构建结果。
        /// </summary>
        /// <param name="Sql">参数化 SQL 语句。</param>
        /// <param name="Params">Dapper 动态参数对象。</param>
        /// <param name="DebugSql">将参数值还原后的可调试 SQL 语句。</param>
        public record SqlInfo(string Sql, DynamicParameters Params, string DebugSql);

公共查询

        /// <summary>
        /// 自动根据 DTO 属性生成 SELECT 字段部分。
        /// 支持物理列和 [Select] 特性定义的虚拟列(由数据库函数生成)。
        /// </summary>
        /// <typeparam name="T">DTO 实体类型。</typeparam>
        /// <param name="sbSql">StringBuilder 实例,用于追加生成的 SELECT 字段列表。</param>
        /// <param name="tableAlias">表别名(可选,如 "t"),用于生成带前缀的列名。</param>
        /// <param name="addNewLine">字段间是否换行(默认 true),用于格式化可读性的 SQL。</param>
        /// <param name="excludeFields">手动指定额外排除的属性名数组(可选)。</param>
        /// <exception cref="ArgumentNullException">sbSql 为 null 时抛出异常。</exception>
        /// <remarks>
        /// 处理优先级:
        /// 1. 优先识别 [Select] 特性定义的虚拟列(数据库函数列)
        /// 2. 其次处理物理列(使用 [Column] 特性或属性名)
        /// 3. 忽略 [NotMapped] 标记和 BaseExcludes 基础排除列表中的属性
        /// 
        /// [Select] 特性中的虚拟列用于 SELECT 构建,INSERT/UPDATE/DELETE 操作时被忽略。
        /// </remarks>
        public static void BuildSelect<T>(
            StringBuilder sbSql,
            string tableAlias = "",
            bool addNewLine = true,
            string[]? excludeFields = null)
        {
            if (sbSql == null)
                throw new ArgumentNullException(nameof(sbSql), "StringBuilder 实例不能为 null");

            string aliasPrefix = string.IsNullOrWhiteSpace(tableAlias) ? "" : $"{tableAlias}.";
            var props = GetFilteredProperties<T>(excludeFields);

            var fieldList = props.Select(prop =>
            {
                // 1. 优先识别 [Select] 特性生成虚拟列(函数列)
                string? selectSql = GetDynamicAttributeValue(prop, "SelectAttribute", "SelectSql");
                if (!string.IsNullOrEmpty(selectSql))
                {
                    ValidateSelectFunction(selectSql, prop.Name);
                    return $"{selectSql} AS {prop.Name}";
                }

                // 2. 处理物理列
                var colAttr = prop.GetCustomAttribute<ColumnAttribute>();
                string columnName = colAttr?.Name ?? prop.Name;
                return $"{aliasPrefix}{columnName} AS {prop.Name}";
            }).ToList();

            string separator = addNewLine ? ",\n " : ", ";
            sbSql.Append(string.Join(separator, fieldList));
        }

        /// <summary>
        /// 构建参数化 WHERE 子句片段。
        /// 支持 [Query] 特性自定义操作符和条件,同时向后兼容传统的字段数组参数(likeFields、inFields)。
        /// </summary>
        /// <typeparam name="T">参数 DTO 类型。</typeparam>
        /// <param name="paramDto">包含查询条件的 DTO 实例。若为 null 返回空 SQL。</param>
        /// <param name="tableAlias">表别名(如 "t"),用于生成带前缀的列名。</param>
        /// <param name="likeFields">需执行模糊查询的属性名数组(向后兼容,优先级低于 [Query] 特性)。</param>
        /// <param name="inFields">需执行 IN 查询的属性名数组(向后兼容,优先级低于 [Query] 特性)。</param>
        /// <param name="excludeFields">需排除构建条件的属性名数组。</param>
        /// <returns>返回包含 WHERE SQL、Dapper 参数对象及调试 SQL 的 SqlInfo 对象。</returns>
        /// <exception cref="ArgumentException">IN 查询条数超过限制或其他参数验证失败时抛出异常。</exception>
        /// <remarks>
        /// 处理优先级(从高到低):
        /// 1. [Query] 特性(新特性,最高优先级,仅用于 WHERE 子句)
        /// 2. 传统的 likeFields 和 inFields 参数(向后兼容)
        /// 3. 自动类型推断(集合类型默认为 IN,其他类型默认为 =)
        /// 4. 默认相等匹配(=)
        /// 
        /// [Query] 特性仅在 BuildWhere 方法中使用,INSERT/UPDATE/DELETE 操作时被忽略。
        /// 支持的操作符:=, !=, <>, <, >, <=, >=, LIKE, IN, IS NULL, IS NOT NULL
        /// 对于 IN 操作符,支持集合、数组、逗号分隔字符串等多种格式。
        /// </remarks>
        public static SqlInfo BuildWhere<T>(
            T? paramDto,
            string tableAlias = "",
            string[]? likeFields = null,
            string[]? inFields = null,
            string[]? excludeFields = null)
        {
            if (paramDto == null)
                return new SqlInfo(string.Empty, new DynamicParameters(), string.Empty);

            var dynamicParams = new DynamicParameters();
            var sbWhere = new StringBuilder();
            string aliasPrefix = string.IsNullOrWhiteSpace(tableAlias) ? "" : $"{tableAlias}.";
            var props = GetFilteredProperties<T>(excludeFields);

            foreach (var prop in props)
            {
                var val = prop.GetValue(paramDto);

                // 优先检查 [Query] 特性
                var queryAttr = prop.GetCustomAttribute<QueryAttribute>();
                if (queryAttr != null)
                {
                    // [Query] 特性存在,使用其定义的操作符和列名
                    string columnName = queryAttr.ColumnName ?? prop.Name;
                    string operand = (queryAttr.Operand ?? "=").ToUpper();

                    // 检查值有效性(对于 IS NULL / IS NOT NULL 操作符跳过值检查)
                    if (operand != "IS NULL" && operand != "IS NOT NULL" && !IsEffectiveValue(val, prop.PropertyType))
                        continue;

                    BuildConditionWithOperand(sbWhere, dynamicParams, aliasPrefix, columnName, prop.Name, operand, val);
                    continue;
                }

                // [Query] 特性不存在,进行值有效性检查
                if (!IsEffectiveValue(val, prop.PropertyType))
                    continue;

                var attr = prop.GetCustomAttribute<ColumnAttribute>();
                string columnName2 = attr?.Name ?? prop.Name;

                // 处理传统的参数数组方式(向后兼容)
                if (inFields != null && inFields.Contains(prop.Name))
                {
                    BuildInCondition(sbWhere, dynamicParams, aliasPrefix, columnName2, prop.Name, val);
                }
                else if (likeFields != null && likeFields.Contains(prop.Name))
                {
                    BuildLikeCondition(sbWhere, dynamicParams, aliasPrefix, columnName2, prop.Name, val);
                }
                else if (val is System.Collections.IEnumerable enumerable && !(val is string))
                {
                    BuildInCondition(sbWhere, dynamicParams, aliasPrefix, columnName2, prop.Name, val);
                }
                else
                {
                    sbWhere.Append($" AND {aliasPrefix}{columnName2} = :{prop.Name}");
                    dynamicParams.Add(prop.Name, val);
                }
            }

            string whereSql = sbWhere.ToString();
            return new SqlInfo(whereSql, dynamicParams, ToDebugSql(whereSql, dynamicParams));
        }

        /// <summary>
        /// 构建完整的单表查询 SQL 语句(包含 SELECT、FROM、WHERE)。
        /// </summary>
        /// <typeparam name="T">DTO 类型。</typeparam>
        /// <param name="paramDto">包含查询条件的 DTO 实例。若为 null 则仅返回 SELECT 和 FROM 部分。</param>
        /// <param name="tableName">物理表名(可选,为 null 时从 [Table] 特性自动获取)。</param>
        /// <param name="tableAlias">表别名(可选,如 "t")。</param>
        /// <param name="likeFields">需执行模糊查询的属性名数组(向后兼容)。</param>
        /// <param name="inFields">需执行 IN 查询的属性名数组(向后兼容)。</param>
        /// <param name="excludeFields">需排除的属性名数组。</param>
        /// <param name="customWhere">手动补充的 SQL WHERE 片段(如 "AND STATUS != 9"),必须以 AND 或 OR 开头。</param>
        /// <returns>返回完整的参数化 SELECT...FROM...WHERE SQL 结果。</returns>
        /// <exception cref="InvalidOperationException">无法自动获取表名或参数验证失败时抛出异常。</exception>
        /// <exception cref="ArgumentException">customWhere 参数格式不正确时抛出异常。</exception>
        public static SqlInfo BuildQuery<T>(
            T? paramDto,
            string? tableName = null,
            string tableAlias = "",
            string[]? likeFields = null,
            string[]? inFields = null,
            string[]? excludeFields = null,
            string? customWhere = null)
        {
            tableName = GetTableName<T>(tableName);
            if (string.IsNullOrWhiteSpace(tableName))
                throw new InvalidOperationException(
                    $"无法获取表名。请在 DTO 类型 '{typeof(T).Name}' 上标注 [Table(\"table_name\")] 特性,或显式传入 tableName 参数。");

            var sbSql = new StringBuilder("SELECT ");
            BuildSelect<T>(sbSql, tableAlias);
            sbSql.Append($"\nFROM {tableName}");

            if (!string.IsNullOrWhiteSpace(tableAlias))
                sbSql.Append($" {tableAlias}");

            sbSql.Append("\nWHERE 1=1 ");

            var whereRes = BuildWhere(paramDto, tableAlias, likeFields, inFields, excludeFields);
            string fullSql = sbSql.ToString() + whereRes.Sql;
            string fullDebugSql = sbSql.ToString() + whereRes.DebugSql;

            if (!string.IsNullOrWhiteSpace(customWhere))
            {
                ValidateCustomWhere(customWhere);
                fullSql += "\n" + customWhere.Trim();
                fullDebugSql += "\n" + customWhere.Trim();
            }

            return new SqlInfo(fullSql, whereRes.Params, fullDebugSql);
        }

        /// <summary>
        /// 构建计数查询 SQL(用于获取总记录数)。
        /// 支持 [Query] 特性的自定义条件,忽略 [Select] 特性。
        /// </summary>
        /// <typeparam name="T">查询参数 DTO 类型。</typeparam>
        /// <param name="paramDto">包含查询条件的 DTO 实例。若为 null 则返回无条件的计数语句。</param>
        /// <param name="tableName">目标表名(可选,为 null 时从 [Table] 特性自动获取)。</param>
        /// <param name="tableAlias">表别名(可选,如 "t")。</param>
        /// <param name="likeFields">需执行模糊查询的字段名数组(向后兼容)。</param>
        /// <param name="excludeFields">需排除的字段名数组。</param>
        /// <param name="customWhere">自定义 WHERE 条件片段(如 "AND CREATE_TIME > SYSDATE-30"),必须以 AND 或 OR 开头。</param>
        /// <param name="countExpression">自定义计数表达式(可选,默认 "COUNT(1)")。</param>
        /// <returns>返回包含参数化 SELECT COUNT...FROM...WHERE SQL 的 SqlInfo 对象。</returns>
        /// <exception cref="InvalidOperationException">无法获取表名或参数验证失败时抛出异常。</exception>
        public static SqlInfo BuildCountQuery<T>(
            T? paramDto,
            string? tableName = null,
            string tableAlias = "",
            string[]? likeFields = null,
            string[]? excludeFields = null,
            string? customWhere = null,
            string countExpression = "COUNT(1)")
        {
            tableName = GetTableName<T>(tableName);
            if (string.IsNullOrWhiteSpace(tableName))
                throw new InvalidOperationException(
                    $"无法获取表名。请在 DTO 类型 '{typeof(T).Name}' 上标注 [Table(\"table_name\")] 特性,或显式传入 tableName 参数。");

            if (string.IsNullOrWhiteSpace(countExpression))
                countExpression = "COUNT(1)";

            var sbSql = new StringBuilder($"SELECT {countExpression}\nFROM {tableName}");

            if (!string.IsNullOrWhiteSpace(tableAlias))
                sbSql.Append($" {tableAlias}");

            sbSql.Append("\nWHERE 1=1");

            var whereRes = BuildWhere(paramDto, tableAlias, likeFields, excludeFields: excludeFields);
            string fullSql = sbSql.ToString() + whereRes.Sql;
            string fullDebugSql = sbSql.ToString() + whereRes.DebugSql;

            if (!string.IsNullOrWhiteSpace(customWhere))
            {
                ValidateCustomWhere(customWhere);
                fullSql += "\n" + customWhere.Trim();
                fullDebugSql += "\n" + customWhere.Trim();
            }

            return new SqlInfo(fullSql, whereRes.Params, fullDebugSql);
        }


        /// <summary>
        /// 构建获取 Oracle 序列值的查询 SQL。
        /// </summary>
        /// <param name="sequenceName">序列名称(如 "SEQ_USER")。不能为 null 或空字符串。</param>
        /// <returns>返回包含 SELECT...NEXTVAL FROM DUAL SQL 的 SqlInfo 对象。</returns>
        /// <exception cref="ArgumentException">序列名为空或空字符串时抛出异常。</exception>
        public static SqlInfo BuildSequenceQuery(string sequenceName)
        {
            if (string.IsNullOrWhiteSpace(sequenceName))
                throw new ArgumentException("序列名不能为空或空字符串", nameof(sequenceName));

            string sql = $"SELECT {sequenceName}.NEXTVAL FROM DUAL";
            var parameters = new DynamicParameters();
            string debugSql = $"-- 获取 Oracle 序列值\n{sql};";

            return new SqlInfo(sql, parameters, debugSql);
        }

 

公共保存方法

        /// <summary>
        /// 构建保存(新增或更新)SQL 语句及参数。
        /// INSERT 和 UPDATE 操作均忽略 [Query] 和 [Select] 特性,仅处理物理列。
        /// </summary>
        /// <typeparam name="T">模型类型。</typeparam>
        /// <param name="model">待保存的数据对象。不能为 null。</param>
        /// <param name="saveType">保存类型:0 表示 INSERT(新增),1 表示 UPDATE(更新)。</param>
        /// <param name="tableName">目标物理表名(可选,为 null 时从 [Table] 特性自动获取)。</param>
        /// <param name="pkFields">主键属性名数组(用于 WHERE 条件和主键识别)。</param>
        /// <returns>返回包含参数化 SQL 和 Dapper 参数的 SqlInfo 对象。</returns>
        /// <exception cref="ArgumentNullException">model 为 null 时抛出异常。</exception>
        /// <exception cref="InvalidOperationException">无法获取表名或未指定有效主键时抛出异常。</exception>
        /// <exception cref="ArgumentException">saveType 无效(不是 0 或 1)或参数验证失败时抛出异常。</exception>
        public static SqlInfo BuildSave<T>(
            T model,
            int saveType,
            string? tableName = null,
            string[]? pkFields = null)
        {
            if (model == null)
                throw new ArgumentNullException(nameof(model), "模型对象不能为 null");

            tableName = GetTableName<T>(tableName);
            if (string.IsNullOrWhiteSpace(tableName))
                throw new InvalidOperationException(
                    $"无法获取表名。请在 DTO 类型 '{typeof(T).Name}' 上标注 [Table(\"table_name\")] 特性,或显式传入 tableName 参数。");

            pkFields ??= GetPrimaryKeyFields<T>();
            if (pkFields == null || pkFields.Length == 0)
                throw new InvalidOperationException(
                    $"未指定主键字段。请通过 pkFields 参数传入,或在属性上标注 [Key] 特性。");

            var props = GetFilteredProperties<T>();
            var pkList = pkFields.Select(x => x.ToLower()).ToList();
            var dynamicParams = new DynamicParameters();
            string sql;

            if (saveType == 0)
            {
                sql = BuildInsertSql(model, props, tableName, pkList, dynamicParams);
            }
            else if (saveType == 1)
            {
                sql = BuildUpdateSql(model, props, tableName, pkList, dynamicParams);
            }
            else
            {
                throw new ArgumentException(
                    $"无效的 saveType 参数:{saveType}。允许的值为 0(INSERT)或 1(UPDATE)。",
                    nameof(saveType));
            }

            return new SqlInfo(sql, dynamicParams, ToDebugSql(sql, dynamicParams));
        }

 

公共删除方法

        /// <summary>
        /// 构建删除 SQL 语句及参数。
        /// 支持单条记录删除(主键值唯一)或基于主键的批量删除(多个主键值)。
        /// </summary>
        /// <param name="tableName">目标物理表名。不能为 null 或空字符串。</param>
        /// <param name="pkField">主键属性名(如 "poId")。不能为 null 或空字符串。</param>
        /// <param name="pkValues">主键值列表。单个值生成 = 条件,多个值生成 IN 条件。不能为 null 或空列表。</param>
        /// <returns>返回包含参数化 DELETE SQL 的 SqlInfo 对象。</returns>
        /// <exception cref="ArgumentException">表名、主键字段名或主键值列表为空时抛出异常。</exception>
        public static SqlInfo BuildDelete(
            string tableName,
            string pkField,
            List<string> pkValues)
        {
            tableName = GetTableName<T>(tableName);
            if (string.IsNullOrWhiteSpace(tableName))
                throw new InvalidOperationException(
                    $"无法获取表名。请在 DTO 类型 '{typeof(T).Name}' 上标注 [Table(\"table_name\")] 特性,或显式传入 tableName 参数。");

            if (string.IsNullOrWhiteSpace(pkField))
                throw new ArgumentException("主键字段名不能为空或空字符串", nameof(pkField));

            if (pkValues == null || pkValues.Count == 0)
                throw new ArgumentException("主键值列表不能为空", nameof(pkValues));

            var dynamicParams = new DynamicParameters();
            string whereCondition;

            if (pkValues.Count == 1)
            {
                whereCondition = $"{pkField} = :pkValue";
                dynamicParams.Add("pkValue", pkValues[0]);
            }
            else
            {
                var paramNames = new List<string>();
                for (int i = 0; i < pkValues.Count; i++)
                {
                    string pName = $"pkVal{i}";
                    paramNames.Add($":{pName}");
                    dynamicParams.Add(pName, pkValues[i]);
                }
                whereCondition = $"{pkField} IN ({string.Join(", ", paramNames)})";
            }

            string sql = $"DELETE FROM {tableName} WHERE {whereCondition}";
            return new SqlInfo(sql, dynamicParams, ToDebugSql(sql, dynamicParams));
        }

其他公共方法

        /// <summary>
        /// 获取调试 SQL。将参数占位符替换为实际值,用于开发调试和日志记录。
        /// </summary>
        /// <param name="sql">参数化 SQL 语句。</param>
        /// <param name="parameters">Dapper 动态参数对象。若为 null 则直接返回 sql。</param>
        /// <returns>返回参数值被还原的可调试 SQL 字符串。</returns>
        public static string GetDebugSql(string sql, DynamicParameters? parameters)
        {
            return ToDebugSql(sql, parameters);
        }

私有辅助方法

        /// <summary>
        /// 获取或提取表名。优先使用显式传入的 tableName,其次查找 [Table] 特性。
        /// </summary>
        /// <typeparam name="T">DTO 类型。</typeparam>
        /// <param name="explicitTableName">显式传入的表名。若不为空则直接使用。</param>
        /// <returns>获取到的表名。如果无法获取(既无参数也无特性)则返回 null。</returns>
        private static string? GetTableName<T>(string? explicitTableName)
        {
            if (!string.IsNullOrWhiteSpace(explicitTableName))
                return explicitTableName;

            var typeFullName = typeof(T).FullName;
            if (typeFullName == null)
                return null;

            if (TableNameCache.TryGetValue(typeFullName, out var cachedName))
                return cachedName;

            var tableAttr = typeof(T).GetCustomAttribute<TableAttribute>();
            var result = tableAttr?.Name;
            TableNameCache.TryAdd(typeFullName, result);
            return result;
        }

        /// <summary>
        /// 尝试从 [Key] 特性自动获取主键字段名数组。
        /// </summary>
        /// <typeparam name="T">DTO 类型。</typeparam>
        /// <returns>主键字段名数组。如果没有找到则返回空数组。</returns>
        private static string[] GetPrimaryKeyFields<T>()
        {
            var props = GetFilteredProperties<T>();
            var keyFields = props
                .Where(p => p.GetCustomAttribute<KeyAttribute>() != null)
                .Select(p => p.Name)
                .ToArray();

            return keyFields;
        }

        /// <summary>
        /// 检查值是否有效(应该参与数据库操作)。
        /// </summary>
        /// <param name="value">要检查的值。</param>
        /// <param name="propertyType">属性的 CLR 类型。</param>
        /// <returns>若值有效则返回 true;否则返回 false。</returns>
        /// <remarks>
        /// 判定规则(值为无效的情况):
        /// - 值为 null
        /// - DateTime/DateTimeOffset 类型(时间戳通常不作为查询条件)
        /// - 空字符串或仅包含空白字符的字符串
        /// - 空 GUID(Guid.Empty)
        /// - 空集合(IEnumerable 但无元素)
        /// </remarks>
        private static bool IsEffectiveValue(object? value, Type propertyType)
        {
            if (value == null)
                return false;

            var dateTimeTypes = new[]
            {
                typeof(DateTime), typeof(DateTime?),
                typeof(DateTimeOffset), typeof(DateTimeOffset?)
            };
            if (dateTimeTypes.Contains(propertyType))
                return false;

            if (propertyType == typeof(string))
                return !string.IsNullOrWhiteSpace(value.ToString());

            if (propertyType == typeof(Guid) || propertyType == typeof(Guid?))
                return !Guid.Empty.Equals(value);

            if (value is System.Collections.IEnumerable enumerable && !(value is string))
            {
                try
                {
                    return enumerable.GetEnumerator().MoveNext();
                }
                catch
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 根据指定的操作符构建 WHERE 条件。
        /// 用于处理 [Query] 特性定义的自定义条件。
        /// </summary>
        /// <param name="sbWhere">WHERE 子句 StringBuilder,用于追加条件。</param>
        /// <param name="dynamicParams">动态参数对象,用于添加参数值。</param>
        /// <param name="aliasPrefix">表别名前缀(如 "t.")。</param>
        /// <param name="columnName">数据库列名。</param>
        /// <param name="paramName">参数名(用于占位符)。</param>
        /// <param name="operand">SQL 操作符,支持:=, <>, !=, <, >, <=, >=, LIKE, IN, IS NULL, IS NOT NULL。</param>
        /// <param name="value">参数值。对于 IS NULL/IS NOT NULL 可为 null。</param>
        private static void BuildConditionWithOperand(
            StringBuilder sbWhere,
            DynamicParameters dynamicParams,
            string aliasPrefix,
            string columnName,
            string paramName,
            string operand,
            object? value)
        {
            operand = operand.ToUpper().Trim();

            // 处理 IS NULL / IS NOT NULL
            if (operand == "IS NULL" || operand == "IS NOT NULL")
            {
                sbWhere.Append($" AND {aliasPrefix}{columnName} {operand}");
                return;
            }

            // 处理 IN 操作符
            if (operand == "IN")
            {
                if (value == null)
                    throw new ArgumentException($"字段 '{paramName}' 的 IN 操作符值不能为 null");

                List<string> items = new List<string>();
                if (value is System.Collections.IEnumerable enumerable && !(value is string))
                {
                    foreach (var item in enumerable)
                    {
                        if (item != null)
                            items.Add(item.ToString() ?? string.Empty);
                    }
                }
                else if (value is string stringValue)
                {
                    items = stringValue
                        .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
                        .Select(x => x.Trim())
                        .Where(x => !string.IsNullOrEmpty(x))
                        .ToList();
                }
                else
                {
                    items.Add(value.ToString() ?? string.Empty);
                }

                if (items.Count == 0)
                    throw new ArgumentException($"字段 '{paramName}' 的 IN 查询条件为空");

                if (items.Count > MaxInClauseSize)
                    throw new ArgumentException(
                        $"字段 '{paramName}' 的 IN 查询条件数量 {items.Count} 超过限制 {MaxInClauseSize}");

                sbWhere.Append($" AND {aliasPrefix}{columnName} IN :{paramName}");
                dynamicParams.Add(paramName, items);
                return;
            }

            // 处理 LIKE 操作符
            if (operand == "LIKE")
            {
                string stringValue = value?.ToString() ?? string.Empty;
                string escapedVal = EscapeLikeWildcards(stringValue);
                // Oracle 特定的 LIKE 语法
                sbWhere.Append($" AND {aliasPrefix}{columnName} LIKE '%' || :{paramName} || '%' ESCAPE '\\'");
                dynamicParams.Add(paramName, escapedVal);
                return;
            }

            // 处理其他操作符(=, <>, !=, <, >, <=, >=)
            sbWhere.Append($" AND {aliasPrefix}{columnName} {operand} :{paramName}");
            dynamicParams.Add(paramName, value);
        }

        /// <summary>
        /// 构建 IN 条件语句。
        /// 支持集合、数组、逗号或分号分隔的字符串等多种格式。
        /// </summary>
        private static void BuildInCondition(
            StringBuilder sbWhere,
            DynamicParameters dynamicParams,
            string aliasPrefix,
            string columnName,
            string paramName,
            object value)
        {
            List<string>? items = null;

            if (value is System.Collections.IEnumerable enumerable && !(value is string))
            {
                items = new List<string>();
                foreach (var item in enumerable)
                {
                    if (item != null)
                        items.Add(item.ToString() ?? string.Empty);
                }
            }
            else if (value is string stringValue)
            {
                items = stringValue
                    .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
                    .Select(x => x.Trim())
                    .Where(x => !string.IsNullOrEmpty(x))
                    .ToList();
            }
            else
            {
                items = new List<string> { value.ToString() ?? string.Empty };
            }

            if (items.Count == 0)
                throw new ArgumentException($"字段 '{paramName}' 的 IN 查询条件为空,请检查输入值。");

            if (items.Count > MaxInClauseSize)
                throw new ArgumentException(
                    $"字段 '{paramName}' 的 IN 查询条件数量 {items.Count} 超过限制 {MaxInClauseSize}。请分批处理。");

            sbWhere.Append($" AND {aliasPrefix}{columnName} IN :{paramName}");
            dynamicParams.Add(paramName, items);
        }

        /// <summary>
        /// 构建 LIKE 条件语句(带特殊字符转义)。
        /// </summary>
        private static void BuildLikeCondition(
            StringBuilder sbWhere,
            DynamicParameters dynamicParams,
            string aliasPrefix,
            string columnName,
            string paramName,
            object value)
        {
            string stringValue = value.ToString() ?? string.Empty;
            string escapedVal = EscapeLikeWildcards(stringValue);
            // Oracle 特定的 LIKE 语法
            sbWhere.Append($" AND {aliasPrefix}{columnName} LIKE '%' || :{paramName} || '%' ESCAPE '\\'");
            dynamicParams.Add(paramName, escapedVal);
        }

        /// <summary>
        /// 构建 INSERT SQL 语句。
        /// 忽略 [Query] 和 [Select] 特性,仅处理物理列。
        /// </summary>
        private static string BuildInsertSql<T>(
            T model,
            IEnumerable<PropertyInfo> props,
            string tableName,
            List<string> pkList,
            DynamicParameters dynamicParams)
        {
            var sbCols = new StringBuilder();
            var sbVals = new StringBuilder();

            foreach (var prop in props)
            {
                // 忽略 [Select] 特性列
                if (HasAttribute(prop, "SelectAttribute"))
                    continue;
                // 忽略 [Query] 特性列
                if (HasAttribute(prop, "QueryAttribute"))
                    continue;

                var val = prop.GetValue(model);
                bool isPk = pkList.Contains(prop.Name.ToLower());

                if (!isPk && !IsEffectiveValue(val, prop.PropertyType))
                    continue;

                var attr = prop.GetCustomAttribute<ColumnAttribute>();
                string colName = attr?.Name ?? prop.Name;

                // 特殊处理:Oracle 序列字符串直接拼接
                if (isPk && val is string seqStr && seqStr.ToUpper().EndsWith(".NEXTVAL"))
                {
                    sbCols.Append($"{colName}, ");
                    sbVals.Append($"{seqStr}, ");
                    continue;
                }

                sbCols.Append($"{colName}, ");
                sbVals.Append($":{prop.Name}, ");
                dynamicParams.Add(prop.Name, val);
            }

            if (sbCols.Length == 0)
                throw new InvalidOperationException(
                    $"新增操作失败:没有可插入的字段值。请检查模型中是否有有效的字段数据。");

            string columnList = sbCols.ToString().TrimEnd(' ', ',');
            string valueList = sbVals.ToString().TrimEnd(' ', ',');
            return $"INSERT INTO {tableName} ({columnList})\nVALUES ({valueList})";
        }

        /// <summary>
        /// 构建 UPDATE SQL 语句。
        /// 忽略 [Query] 和 [Select] 特性,仅处理物理列。
        /// </summary>
        private static string BuildUpdateSql<T>(
            T model,
            IEnumerable<PropertyInfo> props,
            string tableName,
            List<string> pkList,
            DynamicParameters dynamicParams)
        {
            var sbSets = new StringBuilder();
            var sbWheres = new StringBuilder();

            foreach (var prop in props)
            {
                // 忽略 [Select] 特性列
                if (HasAttribute(prop, "SelectAttribute"))
                    continue;
                // 忽略 [Query] 特性列
                if (HasAttribute(prop, "QueryAttribute"))
                    continue;

                var val = prop.GetValue(model);
                var attr = prop.GetCustomAttribute<ColumnAttribute>();
                string colName = attr?.Name ?? prop.Name;
                bool isPk = pkList.Contains(prop.Name.ToLower());

                if (isPk)
                {
                    if (!IsEffectiveValue(val, prop.PropertyType))
                        throw new InvalidOperationException(
                            $"更新操作失败:主键字段 '{prop.Name}' 的值无效或为空。");

                    if (sbWheres.Length > 0)
                        sbWheres.Append(" AND ");

                    sbWheres.Append($"{colName} = :{prop.Name}");
                    dynamicParams.Add(prop.Name, val);
                }
                else
                {
                    if (!IsEffectiveValue(val, prop.PropertyType))
                        continue;

                    sbSets.Append($"{colName} = :{prop.Name}, ");
                    dynamicParams.Add(prop.Name, val);
                }
            }

            if (sbSets.Length == 0)
                throw new InvalidOperationException(
                    "更新操作失败:没有可更新的有效字段值。请检查模型中的非主键字段数据。");

            if (sbWheres.Length == 0)
                throw new InvalidOperationException(
                    "更新操作失败:未指定主键字段条件。请确保模型中包含有效的主键值。");

            string setList = sbSets.ToString().TrimEnd(' ', ',');
            string whereList = sbWheres.ToString();
            return $"UPDATE {tableName} SET {setList}\nWHERE {whereList}";
        }

        /// <summary>
        /// 验证自定义 WHERE 条件的合法性。
        /// 检查条件格式和危险关键字。
        /// </summary>
        private static void ValidateCustomWhere(string customWhere)
        {
            if (!Regex.IsMatch(customWhere.Trim(), @"^(AND|OR)\s", RegexOptions.IgnoreCase))
                throw new ArgumentException(
                    $"自定义 WHERE 条件必须以 AND 或 OR 开头。当前值:'{customWhere.Trim()}'");

            var dangerousKeywords = new[] { "DROP", "DELETE", "TRUNCATE", "CREATE", "ALTER", "EXEC", "EXECUTE" };
            foreach (var keyword in dangerousKeywords)
            {
                if (customWhere.Contains(keyword, StringComparison.OrdinalIgnoreCase))
                    throw new ArgumentException(
                        $"自定义 WHERE 条件包含不允许的关键字 '{keyword}',以防止 SQL 注入。");
            }
        }

        /// <summary>
        /// 验证 [Select] 特性函数的合法性。
        /// 仅检查危险操作(如数据修改操作),允许参数占位符和复杂函数调用。
        /// </summary>
        private static void ValidateSelectFunction(string selectSql, string propertyName)
        {
            if (!selectSql.Contains("(") || !selectSql.Contains(")"))
                throw new InvalidOperationException(
                    $"属性 '{propertyName}' 的 Select 特性格式无效。必须包含函数调用 (),如:'UPPER(column_name)' 或 'fun_get_name_by_code(:orgId, column_name)'。当前值:'{selectSql}'");

            var dangerousPatterns = new[]
            {
                "DROP", "DELETE", "TRUNCATE", "INSERT", "UPDATE", "CREATE", "ALTER", "EXEC", "EXECUTE"
            };

            foreach (var pattern in dangerousPatterns)
            {
                if (Regex.IsMatch(selectSql, $@"\b{pattern}\b", RegexOptions.IgnoreCase))
                    throw new InvalidOperationException(
                        $"属性 '{propertyName}' 的 Select 特性包含不允许的关键字 '{pattern}'。当前值:'{selectSql}'");
            }

            int openCount = selectSql.Count(c => c == '(');
            int closeCount = selectSql.Count(c => c == ')');
            if (openCount != closeCount)
                throw new InvalidOperationException(
                    $"属性 '{propertyName}' 的 Select 特性括号不匹配。开放括号数:{openCount},关闭括号数:{closeCount}。当前值:'{selectSql}'");
        }

        /// <summary>
        /// 转义 LIKE 查询中的特殊通配符字符。
        /// 用于防止特殊字符被解释为通配符。
        /// </summary>
        private static string EscapeLikeWildcards(string value)
        {
            if (string.IsNullOrEmpty(value))
                return value;

            return value
                .Replace("\\", "\\\\")  // 先转义反斜杠
                .Replace("%", "\\%")    // 再转义百分号
                .Replace("_", "\\_");   // 最后转义下划线
        }

        /// <summary>
        /// Dapper 参数还原工具。将 SQL 中的参数占位符替换为实际值的字符串表示。
        /// 用于生成可调试的完整 SQL 语句。
        /// </summary>
        private static string ToDebugSql(string sql, DynamicParameters? parameters)
        {
            if (parameters == null || string.IsNullOrEmpty(sql))
                return sql;

            string debugSql = sql;
            var paramNames = parameters.ParameterNames.OrderByDescending(x => x.Length);

            foreach (var name in paramNames)
            {
                try
                {
                    var pVal = parameters.Get<object>(name);
                    string formattedVal = "NULL";

                    if (pVal != null)
                        formattedVal = FormatParameterValue(pVal);

                    debugSql = Regex.Replace(
                        debugSql,
                        Regex.Escape(":" + name) + @"\b",
                        formattedVal.Replace("$", "$$"),
                        RegexOptions.IgnoreCase
                    );
                }
                catch
                {
                    // 参数转换失败时,保留原始占位符
                }
            }

            return debugSql;
        }

        /// <summary>
        /// 格式化参数值为可用于 SQL 调试的字符串表示。
        /// </summary>
        private static string FormatParameterValue(object value)
        {
            if (value == null)
                return "NULL";

            return value switch
            {
                string str => $"'{str.Replace("'", "''")}'",
                DateTime dt => $"to_date('{dt:yyyy-MM-dd HH:mm:ss}', 'yyyy-mm-dd hh24:mi:ss')",
                DateTimeOffset dto => $"to_date('{dto:yyyy-MM-dd HH:mm:ss}', 'yyyy-mm-dd hh24:mi:ss')",
                bool b => b ? "1" : "0",
                System.Collections.IEnumerable enumerable when !(value is string) =>
                    FormatCollectionValue(enumerable),
                _ => value.ToString() ?? "NULL"
            };
        }

        /// <summary>
        /// 格式化集合值为 Oracle SQL 列表形式。
        /// </summary>
        private static string FormatCollectionValue(System.Collections.IEnumerable enumerable)
        {
            var items = new List<string>();
            try
            {
                foreach (var item in enumerable)
                {
                    if (item is string sItem)
                        items.Add($"'{sItem.Replace("'", "''")}'");
                    else if (item != null)
                        items.Add(item.ToString() ?? "NULL");
                    else
                        items.Add("NULL");
                }
            }
            catch
            {
                return "NULL";
            }

            return items.Count > 0 ? string.Join(", ", items) : "NULL";
        }

        /// <summary>
        /// 获取经筛选后的属性集合。
        /// 排除 BaseExcludes、[NotMapped] 特性及指定的额外排除字段。
        /// </summary>
        private static IEnumerable<PropertyInfo> GetFilteredProperties<T>(string[]? extraExcludes = null)
        {
            var typeFullName = typeof(T).FullName;
            if (typeFullName == null)
                return Enumerable.Empty<PropertyInfo>();

            var allProps = PropertyCache.GetOrAdd(typeFullName, _ =>
            {
                var type = typeof(T);
                return type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
            });

            return allProps.Where(p =>
                !BaseExcludes.Contains(p.Name) &&
                (extraExcludes == null || !extraExcludes.Contains(p.Name)) &&
                p.GetCustomAttribute<NotMappedAttribute>() == null);
        }

        /// <summary>
        /// 动态获取指定特性的属性值。
        /// 解决同名特性在不同命名空间下的冲突问题。
        /// </summary>
        private static string? GetDynamicAttributeValue(PropertyInfo prop, string attrName, string propertyName)
        {
            try
            {
                var attr = prop.GetCustomAttributes()
                    .FirstOrDefault(a => a.GetType().Name == attrName);

                if (attr == null)
                    return null;

                var propertyInfo = attr.GetType().GetProperty(
                    propertyName,
                    BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase
                );

                return propertyInfo?.GetValue(attr)?.ToString();
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 判断属性是否标注了指定的特性。
        /// 基于特性类名字符串匹配,不依赖完全限定名。
        /// </summary>
        private static bool HasAttribute(PropertyInfo prop, string attrName)
        {
            return prop.GetCustomAttributes()
                .Any(a => a.GetType().Name == attrName);
        }

自定义特性

    /// <summary>
    /// SELECT 虚拟列特性。用于标记属性为 SELECT 字段中的计算列(由数据库函数生成)。
    /// </summary>
    /// <remarks>
    /// <para>特点:</para>
    /// <list type="bullet">
    /// <item><description>仅在 BuildSelect 方法中使用,生成 SELECT 列表</description></item>
    /// <item><description>在 INSERT、UPDATE、DELETE 操作中被忽略</description></item>
    /// <item><description>支持参数占位符如 :paramName,支持复杂函数调用</description></item>
    /// <item><description>支持嵌套函数和包名.函数的格式</description></item>
    /// </list>
    /// 
    /// <para>使用示例:</para>
    /// <code>
    /// // 简单函数
    /// [Select("UPPER(user_name)")]
    /// public string UserNameUpper { get; set; }
    /// 
    /// // 带参数占位符的函数
    /// [Select("fun_get_name_by_code('user', :orgId, AUDIT_BY)")]
    /// public string AuditByName { get; set; }
    /// 
    /// // 嵌套函数
    /// [Select("UPPER(SUBSTR(user_name, 1, 10))")]
    /// public string UserNameShort { get; set; }
    /// 
    /// // 包名.函数格式
    /// [Select("pkg_util.fn_get_user_info(:userId)")]
    /// public string UserInfo { get; set; }
    /// </code>
    /// </remarks>
    [AttributeUsage(AttributeTargets.Property)]
    public sealed class SelectAttribute : Attribute
    {
        /// <summary>
        /// 初始化 SelectAttribute 实例。
        /// </summary>
        /// <param name="selectSql">
        /// SQL 函数调用字符串。
        /// 支持:
        /// - 简单函数:UPPER(column_name)
        /// - 带字面量:SUBSTR(column_name, 1, 10)
        /// - 带参数占位符:fun_get_name_by_code('user', :orgId, column_name)
        /// - 嵌套函数:UPPER(SUBSTR(column_name, 1, 10))
        /// - 包名.函数:pkg_util.fn_get_info(:param)
        /// </param>
        /// <exception cref="ArgumentException">selectSql 为空或空字符串时抛出异常。</exception>
        public SelectAttribute(string selectSql)
        {
            if (string.IsNullOrWhiteSpace(selectSql))
                throw new ArgumentException("Select 函数不能为空或空字符串", nameof(selectSql));

            SelectSql = selectSql;
        }

        /// <summary>
        /// 获取 SQL 函数调用字符串。
        /// </summary>
        public string SelectSql { get; }
    }

    /// <summary>
    /// 查询条件特性。用于标记属性在 WHERE 子句中的自定义条件和 SQL 操作符。
    /// </summary>
    /// <remarks>
    /// <para>特点:</para>
    /// <list type="bullet">
    /// <item><description>仅在 BuildWhere 方法中使用,生成 WHERE 条件</description></item>
    /// <item><description>在 INSERT、UPDATE、DELETE 操作中被忽略</description></item>
    /// <item><description>支持多种 SQL 操作符,包括比较、模糊、IN 等</description></item>
    /// <item><description>支持自定义列名,与属性名不同的数据库列</description></item>
    /// <item><description>注:若处理相等时 可采用 [Column("id")] 等同于 [Query("id")]</description></item>
    /// </list>
    /// 
    /// <para>支持的操作符:</para>
    /// <list type="bullet">
    /// <listheader><term>操作符</term><description>说明 (示例)</description></listheader>
    /// <item><term>=</term><description>相等(默认)。例:[Query("id")]</description></item>
    /// <item><term>!=</term><description>不等于。例:[Query("status", "!=")]</description></item>
    /// <item><term>&lt;&gt;</term><description>不等于。例:[Query("status", "&lt;&gt;")]</description></item>
    /// <item><term>&lt;</term><description>小于。例:[Query("createTime", "&lt;")]</description></item>
    /// <item><term>&gt;</term><description>大于。例:[Query("createTime", "&gt;")]</description></item>
    /// <item><term>&lt;=</term><description>小于等于。例:[Query("endTime", "&lt;=")]</description></item>
    /// <item><term>&gt;=</term><description>大于等于。例:[Query("startTime", "&gt;=")]</description></item>
    /// <item><term>LIKE</term><description>模糊查询。例:[Query("name", "LIKE")]</description></item>
    /// <item><term>IN</term><description>包含。例:[Query("status", "IN")]</description></item>
    /// <item><term>IS NULL</term><description>空值判断。例:[Query("deletedAt", "IS NULL")]</description></item>
    /// <item><term>IS NOT NULL</term><description>非空值判断。例:[Query("deletedAt", "IS NOT NULL")]</description></item>
    /// </list>
    /// 
    /// <para>使用示例:</para>
    /// <code>
    /// // 简单相等
    /// [Query("user_id")]
    /// public string UserId { get; set; }
    /// // 生成:AND user_id = :UserId
    /// 
    /// // 比较操作符 (注意:在 C# 代码字符串中可以直接写 &lt; 和 &gt;)
    /// [Query("CreateTime", "&lt;=")]
    /// public DateTime? Search_EndTime { get; set; }
    /// // 生成:AND t.CreateTime &lt;= :Search_EndTime
    /// 
    /// // IN 查询(支持集合)
    /// [Query("Status", "IN")]
    /// public List&lt;int&gt; StatusList { get; set; }
    /// // 生成:AND Status IN (:StatusList)
    /// </code>
    /// </remarks>
    [AttributeUsage(AttributeTargets.Property)]
    public sealed class QueryAttribute : Attribute
    {
        /// <summary>
        /// 初始化 QueryAttribute 实例。
        /// </summary>
        /// <param name="columnName">
        /// 对应的数据库列名。
        /// 若为 null 或空则使用属性名。
        /// </param>
        /// <param name="operand">
        /// SQL 操作符。
        /// 支持的值:=, !=, <>, <, >, <=, >=, LIKE, IN, IS NULL, IS NOT NULL
        /// 默认为 "=" (相等)。大小写不敏感,会自动转换为大写。
        /// </param>
        /// <exception cref="ArgumentException">columnName 为空或空字符串时抛出异常。</exception>
        public QueryAttribute(string columnName, string? operand = "=")
        {
            if (string.IsNullOrWhiteSpace(columnName))
                throw new ArgumentException("列名不能为空或空字符串", nameof(columnName));

            ColumnName = columnName;
            Operand = operand ?? "=";
        }

        /// <summary>
        /// 获取对应的数据库列名。
        /// </summary>
        public string ColumnName { get; }

        /// <summary>
        /// 获取 SQL 操作符(如 =, LIKE, IN 等)。
        /// 大小写不敏感,会在使用时自动转换为大写。
        /// </summary>
        public string Operand { get; }
    }

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2026年2月8日

tianxiazui

Believe Meet Will Meeting 相信一期一会

点赞

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2024 天下醉丶. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

琼ICP备19000584号-1