VOL 框架实用技巧:C# 字符串自动 Trim 工具类源码与应用解析

在实际开发中,数据库 CHAR 类型字段常常会因为定长特性而自动填充空格,导致读取数据时字符串尾部出现多余空格。为此,本文分享一个通用的字符串自动 Trim 工具类 StringTrimmer,可递归处理对象及集合中的所有字符串属性。

源码

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Reflection;

namespace VolPro.Core.Utilities
{
    /// <summary>
    /// 字符串自动 Trim 工具类,专门处理 CHAR 类型字段的空格问题
    /// </summary>
    public static class StringTrimmer
    {
        private static readonly Dictionary<Type, PropertyInfo[]> StringPropertyCache = new();

        /// <summary>
        /// 自动 Trim 任意对象中的所有 string 字段
        /// 支持单个对象、List、IEnumerable 等集合类型
        /// </summary>
        public static void TrimAllStringProperties<T>(T obj)
        {
            var visited = new HashSet<object>();
            TrimAllStringPropertiesInternal(obj, visited);
        }

        private static void TrimAllStringPropertiesInternal(object obj, HashSet<object> visited)
        {
            if (obj == null || obj is string || visited.Contains(obj))
                return;

            visited.Add(obj);

            // 处理集合类型
            if (obj is IEnumerable enumerable)
            {
                foreach (var item in enumerable)
                {
                    if (item != null)
                        TrimAllStringPropertiesInternal(item, visited);
                }
            }
            else
            {
                TrimObject(obj);

                var type = obj.GetType();
                var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                    .Where(p => p.CanRead && p.GetIndexParameters().Length == 0);

                foreach (var property in properties)
                {
                    if (property.PropertyType == typeof(string) ||
                        IsSimpleType(property.PropertyType) ||
                        property.GetCustomAttribute<NotMappedAttribute>() != null)
                        continue;

                    var value = property.GetValue(obj);
                    if (value == null) continue;

                    TrimAllStringPropertiesInternal(value, visited);
                }
            }
        }

        /// <summary>
        /// Trim 单个对象的所有字符串属性
        /// </summary>
        private static void TrimObject(object obj)
        {
            var type = obj.GetType();
            var stringProperties = GetStringProperties(type);

            foreach (var property in stringProperties)
            {
                try
                {
                    var value = property.GetValue(obj) as string;
                    if (!string.IsNullOrEmpty(value))
                    {
                        bool isCharType = IsCharTypeProperty(property);

                        if (isCharType || HasTrailingSpaces(value))
                        {
                            property.SetValue(obj, value.Trim());
                        }
                    }
                }
                catch
                {
                    continue;
                }
            }
        }

        /// <summary>
        /// 从缓存中获取字符串属性
        /// </summary>
        private static PropertyInfo[] GetStringProperties(Type type)
        {
            if (StringPropertyCache.TryGetValue(type, out var props))
                return props;

            props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
                .Where(p =>
                    p.CanRead &&
                    p.CanWrite &&
                    p.PropertyType == typeof(string) &&
                    p.GetCustomAttribute<NotMappedAttribute>() == null)
                .ToArray();

            StringPropertyCache[type] = props;
            return props;
        }

        /// <summary>
        /// 判断属性是否为 CHAR 类型
        /// </summary>
        private static bool IsCharTypeProperty(PropertyInfo property)
        {
            var columnAttribute = property.GetCustomAttribute<ColumnAttribute>();
            if (columnAttribute != null && !string.IsNullOrEmpty(columnAttribute.TypeName))
            {
                var typeName = columnAttribute.TypeName.ToLower();
                return typeName.StartsWith("char(") || typeName == "char";
            }

            return false;
        }

        /// <summary>
        /// 判断字符串是否存在尾部空格
        /// </summary>
        private static bool HasTrailingSpaces(string value)
        {
            return value.AsSpan()[^1] == ' ';
        }

        /// <summary>
        /// 判断是否为不可递归的简单类型
        /// </summary>
        private static bool IsSimpleType(Type type)
        {
            return type.IsPrimitive ||
                   type.IsEnum ||
                   type == typeof(string) ||
                   type == typeof(decimal) ||
                   type == typeof(DateTime) ||
                   type == typeof(Guid) ||
                   type == typeof(byte[]);
        }

        /// <summary>
        /// 批量处理 PageGridData 中的数据
        /// </summary>
        public static void TrimPageGridData<T>(Entity.DomainModels.PageGridData<T> pageData) where T : class
        {
            if (pageData?.rows != null)
            {
                TrimAllStringProperties(pageData.rows);
            }
        }
    }
}

说明

  • 支持递归处理对象和集合中的所有字符串属性。
  • 针对 CHAR 类型字段和字符串尾部空格自动 Trim。
  • 适用于数据库实体、DTO、分页数据等多种场景。

欢迎在实际项目中使用和改进!

本工具类是针对 VOL 框架的数据实体设计,适用于 VOL 项目中的数据库 CHAR 类型字段自动去除尾部空格。如果你在其他框架或项目中使用,请根据实际数据结构和属性特性进行适当修改和调整,以确保兼容性和正确性。
最后修改:2025 年 08 月 06 日
如果觉得我的文章对你有用,请随意赞赏